[!NOTE] This repository uses the Aspect CLI for CI and local development. See the docs and install instructions to get started.
[!WARNING] This is the 2.x ALPHA branch. APIs and behavior may change without notice. For stable documentation, see the 1.x branch.
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//dep_group=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
//pyand//uvextension paths provide stable APIs for interpreter provisioning and dependency resolution. They graduated from//py/unstableand//uv/unstablein rules_py v2.0.0.
interpreters = use_extension("@aspect_rules_py//py:extensions.bzl", "python_interpreters")
interpreters.toolchain(python_version = "3.12")
use_repo(interpreters, "python_interpreters")
register_toolchains("@python_interpreters//:all")
# .bazelrc
common --@aspect_rules_py//py:python_version=3.12
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_testpytest-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:extensions.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,
)
# 3c. (Optional) Remove bundled tests or other unused wheel content
uv.override_package(
name = "another_package",
lock = "//:uv.lock",
exclude_glob = ["another_package/**/tests/**"],
)
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//dep_group=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.
In v2.0 the venv targets that the v1.x docs auto-emitted alongside every
py_binary are opt-in — declare them on the targets you actually want your
IDE to follow.
The recommended one-liner:
load("@aspect_rules_py//py:defs.bzl", "py_binary")
py_binary(
name = "my_app",
srcs = ["main.py"],
main = "main.py",
expose_venv_link = True, # publishes :my_app.venv + :my_app.venv_link
)
expose_venv_link = True emits two sibling targets:
bazel run //:my_app.venv — drops you into the hermetic interpreter REPL
with the venv activated. Useful for ad-hoc Python sessions matching your
binary's deps.bazel run //:my_app.venv_link — materialises a workspace-local symlink
pointing at the target's complete runfiles tree and prints the venv's nested
path below that link. Point your IDE at the printed venv path.Then point your IDE to the virtualenv path printed by the command:
python.defaultInterpreterPath to the printed pathpython-lsp-server or pyright to use the virtualenvexpose_venv_link = True is sugar for the explicit two-target shape:
py_binary(
name = "my_app",
srcs = ["main.py"],
main = "main.py",
expose_venv = True,
)
py_venv_link(
name = "my_app.venv_link",
venv = ":my_app.venv",
)
Reach for the explicit form when you want to customise py_venv_link's
link_name, point it at a standalone py_venv (declared independently
of any binary, useful for an IDE-only environment), or selectively skip
the link target on a subset of binaries.
Migrating from v1.x?
py_binaryno longer auto-emits a.venvsibling. Addexpose_venv_link = Truefor the equivalent IDE-symlink behavior, or use the explicit two-target form when you want fine-grained control. The workspace link now points to the complete runfiles tree, so update IDE, shell, direnv, and automation paths such as.venv/binto use the nested virtualenv path printed bybazel run :<name>.venv_link.
Attach DAP-compatible debuggers (VSCode, PyCharm, Neovim, etc.) using
debugpy. This requires a wrapper
entrypoint that starts a debugpy listener before running your application —
simply adding debugpy to deps is not enough.
See the complete debugger example for a working
setup, including a py_debuggable_binary macro that handles the wrapper
generation automatically.
Quick overview:
cd examples/debugger
# Start with debugpy listener, wait for IDE to attach:
DEBUGPY_WAIT=1 bazel run //:app
# Release mode — no debugpy, runs directly:
bazel run //:app --config=release
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] For pytest suites, use
py_pytest_test(always drives pytest) instead of the genericpy_test. Because Gazelle can't infer thepytestdependency for assert-only tests, mappy_testto a thin wrapper that injects it rather than mapping directly — see docs/test-drivers.md.py_pytest_test/py_unittest_testrequire a release that includes them (later than the1.11.2pinned above).
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.declare_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_py2.0.0-alpha.5 +25d2026-07-22 | |
2.0.0-alpha.4 +11d2026-06-27 | |
2.0.0-alpha.3 +2d2026-06-16 | |
1.11.7 +22d2026-06-13 | |
2.0.0-alpha.1 +16d2026-05-22 | |
1.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 |