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.
aspect_rules_py is a near drop-in replacement for rules_python. The same
rule names of py_binary, py_library, and py_test work with the same
attributes, so most projects can migrate with only a few targeted changes.
Switching to uv for dependency management is optional but unlocks
cross-platform lockfiles, faster builds, and container image support.
The four layers of a Python build
A Bazel Python build has four layers. This table shows what handles each one in the legacyrules_python versus the Aspect-recommended approach:
| Layer | Legacy | Recommended |
|---|---|---|
| Interpreter: fetch a hermetic Python binary | rules_python | aspect_rules_py |
| Dependencies: install packages from PyPI | rules_python + pip | aspect_rules_py + uv |
Rules: py_binary, py_library, py_test | rules_python | aspect_rules_py |
| Gazelle: auto-generate BUILD files | rules_python | aspect-gazelle |
aspect_rules_py now provides its own interpreter provisioning. All four layers are where aspect_rules_py makes significant improvements.
Step 1: Add aspect_rules_py to MODULE.bazel
Addaspect_rules_py as a dependency alongside your existing rules_python:
Step 2: Update load statements
The minimal migration involves replacingload() statements in your BUILD files. The rule names stay the same, only the source changes:
srcs, deps, data, and args, stay exactly the same.
Step 3: Update Gazelle if you use it
If you use Gazelle to auto-generate BUILD files, add these directives to any ancestorBUILD file
of your Python code. They tell Gazelle to write aspect_rules_py load()
statements instead of rules_python ones going forward:
Step 4: Migrate to uv
Aspect highly recommends this optional step.uv replaces pip.parse
as the dependency management layer, which gives you a single cross-platform
lockfile, faster builds, and support for cross-platform container builds.
Before: pip.parse
After: uv
First, if you don’t already have apyproject.toml, create one:
pip.parse block in MODULE.bazel with:
.bazelrc. Use the name from your pyproject.toml:
@my_deps//... to @pypi//...:
[dependency-group] in your pyproject.toml becomes a named build configuration. This is a significant improvement over pip.parse, which required a separate requirements_lock.txt per Python version and had no concept of switching dependency sets at build time. With uv, you can switch between configurations — production, dev, testing — with a single flag:
Features available after migrating
aspect_rules_py adds capabilities that rules_python does not support:
- per-target Python version pinning
- cross-platform container builds
- IDE support through real virtualenvs
Pin a specific Python version per target
Bazel normally uses a single Python version across the entire build, set globally inMODULE.bazel. In a monorepo, this becomes an issue when, for example, different teams are on different versions, or when you need to validate that a library still works on an older runtime before upgrading.
aspect_rules_py adds a python_version attribute to py_binary and py_test, so each target can declare the version it needs independently of everything else in the repository.
Swap in a local or patched package
To replace a PyPI package with a local Bazel target for vendoring, patching, or iterating on a fork, useuv.override_package in MODULE.bazel:
@pypi//cowsay uses your local version instead of the one from PyPI. See Swapping in a local package in the uv guide for details.
Cross-platform container builds
A rules_python limitationrules_python cannot produce correct Linux container images from a Mac. When pip installs packages, it selects wheels for the host platform. On a Mac, that means macOS wheels: compiled extensions linked against macOS system libraries, with .dylib references and platform-specific binaries. When those artifacts are packaged into a container and run on Linux, the result is a crash or silent misbehavior. This is a fundamental architectural limitation of rules_python, not a configuration problem.
How aspect_rules_py solves it
aspect_rules_py with uv was built specifically to solve this. uv selects wheels for the target platform regardless of where the build is running. Declare a target platform, and Bazel fetches the correct Linux wheels, links against the correct Linux system libraries, and produces an image that works on Linux from your Mac, without Docker installed.
IDE support out of the box
aspect_rules_py creates a real Python virtualenv for each build. PyCharm, VS Code, and other editors can discover it automatically, which gives you working autocomplete and jump-to-definition without any extra IDE configuration.
Compatibility notes
rules_python and aspect_rules_py can coexist. You don’t have to migrate everything at once. You can load py_binary from aspect_rules_py in one package while another package still uses rules_python. Migrate incrementally as it suits your team.
Entrypoints require manual declaration. With pip.parse, such as command-line scripts installed by packages like ruff or black, pip detects entrypoints automatically at setup time. With uv, installs happen during the build, so you must declare entrypoints as Bazel targets if you need to run them directly.
setuptools and build must be in your lockfile. uv needs these tools to compile packages that don’t have pre-built wheels. Add them to your pyproject.toml to avoid configuration errors:

