aspect_rules_py is a high-performance alternative to rules_python, the
reference Python ruleset for Bazel.
It provides drop-in replacements for py_binary, py_library, and py_test that prioritize:
uv integrationsite-packages symlink treesaspect_rules_py optimizes for modern Python development workflows, large-scale monorepos, and Remote Build Execution (
RBE) environments.
rules_python| Feature | rules_python | rules_py |
|---|---|---|
| Dependency resolution | pip.parse (repo rules, loading phase) |
Build-action wheel installs (whl_install) |
| uv integration | uv pip compile → requirements.txt → pip.parse |
Native uv.lock consumption |
| Cross-platform lockfile | requirements.txt (uv.lock via uv pip compile) |
Native single uv.lock consumption |
| sdist / PEP 517 builds | Not supported (#2410, open since Nov 2024) | Build actions (pep517_whl, pep517_native_whl) |
| Interpreter provisioning | Download via rules_python extension | Own python-build-standalone extension — no rules_python required |
| Site-packages layout | Standard site-packages layout (flag-enabled) |
Standard site-packages symlink tree |
| Cross-compilation | Limited | Native platform transitions (e.g. arm64 image on amd64 host) |
| Virtual dependencies | No | virtual_deps — swap implementations at binary level |
| PEP 735 dependency groups | No | --@pypi//venv=prod flag |
[!NOTE] rules_python's uv support:
rules_python's uv integration runsuv pip compileas a build action to generate arequirements.txt—it is a fasterpip-compilereplacement. The result still feeds intopip.parse()→whl_libraryrepository rules at loading phase. There is nouv.lockconsumption; the rules_python maintainer has suggested this work belongs in a dedicated project.
uv.lock Dependency ResolutionInstead of relying on legacy pip machinery, we provide native integration with uv,
a Rust-native Python package resolver.
aarch64 wheels on a macOS x86_64 host)uv.lock parsing: Consumes uv.lock directly; no requirements.txt generation stepuv.lock works across all platformsprod, dev, test dependency groups and switch between them with a flagpy_library targets via uv.override_package()aspect_rules_py ships its own python-build-standalone
interpreter extension—rules_python is not required as a toolchain provider.
[!NOTE] The
//py/unstableand//uv/unstableextension paths indicate that the extension API may evolve across releases—not that the features are unstable.
interpreters = use_extension("@aspect_rules_py//py/unstable:extension.bzl", "python_interpreters")
interpreters.toolchain(python_version = "3.12", is_default = True)
use_repo(interpreters, "python_interpreters")
register_toolchains("@python_interpreters//:all")
This enables cross-compilation from any host to any target without host-installed Python, and is the foundation for correct toolchain selection in RBE environments.
site-packages LayoutWe do not manipulate sys.path or $PYTHONPATH. Instead, we generate a standard site-packages directory structure
using symlink trees:
collections vs. a transitive dependency named collections)-I flag, preventing implicit loading of user site-packages or host
environment variablesamd64 and arm64 container imagesvirtual_deps allow external Python dependencies to be specified by package name rather than by label:
cowsnake instead of cowsay)Built-in rules for creating optimized container images:
py_image_layer: Creates layered tar files compatible with rules_ocipy_pytest_mainpytest-mock, pytest-xdist, and other pluginsbazel_dep(name = "aspect_rules_py", version = "1.11.2")
Load rules from aspect_rules_py in your BUILD files:
load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_library", "py_test")
py_library(
name = "lib",
srcs = ["lib.py"],
deps = ["@pypi//requests"],
)
py_binary(
name = "app",
srcs = ["main.py"],
main = "main.py",
deps = [":lib"],
)
py_test(
name = "test",
srcs = ["test.py"],
deps = [":lib"],
)
uvaspect_rules_py//uv is our alternative to rules_python's pip.parse:
uv = use_extension("@aspect_rules_py//uv/unstable:extension.bzl", "uv")
# 1. Declare a hub (a shared dependency namespace)
uv.declare_hub(
hub_name = "pypi",
)
# 2. Register projects (lockfiles) into the hub
uv.project(
hub_name = "pypi",
lock = "//:uv.lock",
pyproject = "//:pyproject.toml",
# Build tools injected for sdist packages that need them (e.g. maturin, setuptools)
default_build_dependencies = ["build", "setuptools"],
)
# 3a. (Optional) Replace a package with a local Bazel target
uv.override_package(
name = "some_package",
lock = "//:uv.lock",
target = "//third_party/some_package",
)
# 3b. (Optional) Patch an installed wheel's file tree after unpacking
uv.override_package(
name = "some_other_package",
lock = "//:uv.lock",
post_install_patches = ["//third_party/patches:fix_some_other_package.patch"],
post_install_patch_strip = 1,
)
use_repo(uv, "pypi")
Requirements are declared in standard pyproject.toml:
[project]
name = "myapp"
version = "1.0.0"
requires-python = ">= 3.11"
dependencies = [
"requests>=2.28",
"pydantic>=2.0",
]
[dependency-groups]
dev = ["pytest", "black", "mypy"]
Generate the lockfile with uv:
uv lock
Switch between dependency groups:
# Default: use all dependencies
bazel run //:app
# Use only production dependencies
bazel run //:app --@pypi//venv=prod
Declare virtual dependencies in libraries:
py_library(
name = "greet_lib",
srcs = ["greet.py"],
virtual_deps = ["cowsay"], # Not a label—just a package name
)
Resolve them in binaries:
py_binary(
name = "app",
srcs = ["main.py"],
deps = [":greet_lib"],
resolutions = {
"cowsay": "@pypi//cowsay",
},
)
# Or use a different implementation!
py_binary(
name = "app_snake",
srcs = ["main.py"],
deps = [":greet_lib"],
resolutions = {
"cowsay": "//cowsnake", # Swapped implementation
},
)
Build optimized OCI images with layer caching:
load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_image_layer")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")
py_binary(
name = "app_bin",
srcs = ["main.py"],
deps = ["//:lib"],
)
py_image_layer(
name = "app_layers",
binary = ":app_bin",
)
oci_image(
name = "image",
base = "@ubuntu",
tars = [":app_layers"],
entrypoint = ["/app/app_bin"],
)
oci_load(
name = "image_load",
image = ":image",
repo_tags = ["myapp:latest"],
)
Cross-compile for Linux from macOS:
bazel build //:image --platforms=//platforms:linux_amd64
aspect_rules_py generates standard virtualenv structures that IDEs understand:
# Creates a .venv symlink for the target
bazel run //:my_app.venv
Then point your IDE to the generated virtualenv:
python.defaultInterpreterPath to the .venv path.venv as a Python interpreterpython-lsp-server or pyright to use the virtualenvAttach debuggers using debugpy:
# In debug mode, wraps the binary with debugpy listener
py_binary(
name = "app_debug",
srcs = ["main.py"],
deps = ["//:lib", "@pypi//debugpy"],
env = {"DEBUGPY_WAIT": "1"}, # Wait for IDE attachment
)
VSCode launch.json:
{
"name": "Attach to Bazel py_binary",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "127.0.0.1",
"port": 5678
}
}
Generate BUILD files automatically with the Gazelle extension:
# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.42.0")
bazel_dep(name = "aspect_rules_py", version = "1.11.2")
# In your BUILD file
# gazelle:map_kind py_library py_library @aspect_rules_py//py:defs.bzl
# gazelle:map_kind py_binary py_binary @aspect_rules_py//py:defs.bzl
# gazelle:map_kind py_test py_test @aspect_rules_py//py:defs.bzl
# Generate BUILD files
bazel run //:gazelle
[!NOTE] When using
pytest_main=True, you can create a small wrapper macro forpy_testthat presetspytest_main=Trueand automatically addspytestas dep. Then you need to updategazelle:map_kind py_test py_testto point to your wrapper rule.
rules_pythonaspect_rules_py is designed for incremental adoption:
py_binary, py_library, py_test from @aspect_rules_py//py:defs.bzl instead of
@rules_python//python:defs.bzlpip.parse with uv.hub and generate a uv.lockrules_python interpreter provisioning with
the aspect_rules_py interpreter extension for fully independent hermetic interpretersFor detailed migration guidance, see docs/migrating.md.
uv| Layer | Implementation | Description |
|---|---|---|
| Toolchains | @aspect_rules_py//py |
Own python-build-standalone interpreter provisioning; @rules_python optional |
| Resolution | @aspect_rules_py//uv |
Fast, lockfile-backed dependency resolution with uv |
| Execution | @aspect_rules_py//py |
Drop-in replacements for py_binary, py_library, py_test with sandbox isolation |
| Generation | aspect-gazelle |
Pre-compiled Gazelle extension—no CGO toolchain required |
Apache 2.0 - see LICENSE
More compatible Bazel rules for running Python tools and building Python projects
@aspect-build/rules_py1.11.5 +28d2026-05-05 | |
1.11.2 +21h2026-04-07 | |
1.11.1 +20h2026-04-06 | |
1.10.1 +16d2026-04-05 | |
1.10.0 +8d2026-03-20 | |
1.9.1 +13h2026-03-11 | |
1.9.0 +1d2026-03-11 | |
1.8.6 +3d2026-03-09 | |
1.8.5 +1.6mo2026-03-05 | |
1.8.3 +2.8mo2026-01-15 | |
1.6.5 +5d2025-10-15 | |
1.6.4-rc2 +14d2025-09-24 | |
1.6.4-rc1 +24d2025-09-09 | |
1.6.3 +1d2025-08-16 | |
1.6.2 +20d2025-08-14 | |
1.6.1 +1.4mo2025-07-24 | |
1.6.0 +20h2025-06-10 | |
1.6.0-rc0 +9d2025-06-09 | |
1.5.2 +9h2025-05-31 | |
1.5.1 +1d2025-05-30 | |
1.5.0 +19d2025-05-28 | |
1.4.0 +14d2025-05-09 | |
1.3.4 +3h2025-04-24 | |
1.3.3 +2.3mo2025-04-24 | |
1.3.2 +14d2025-02-13 | |
1.3.1 +22h2025-01-29 | |
1.3.0 +4d2025-01-28 | |
1.2.1 +11h2025-01-24 | |
1.2.0 +6d2025-01-24 | |
1.1.0 +2.1mo2025-01-17 | |
1.0.0-rc1 +21d2024-11-01 | |
1.0.0-rc0 +6d2024-10-11 | |
0.9.1 +2d2024-10-04 | |
0.8.2 +16h2024-10-02 | |
0.8.1 +26d2024-10-02 | |
0.8.0 +1.4mo2024-09-05 | |
0.7.4 +2.8mo2024-07-23 | |
0.7.2 +1.8mo2024-04-26 | |
0.4.0 +4.6mo2023-10-19 | |
0.3.02023-06-02 |