Skip to main content
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, and adds cross-platform lockfiles, faster builds, and cross-platform container builds.

The four layers of a Python build

A Bazel Python build has four layers. This table shows what handles each one in the legacy rules_python versus the Aspect-recommended approach: aspect_rules_py provisions its own interpreters, so once all four layers are migrated, rules_python is no longer a direct dependency.

Step 1: Add aspect_rules_py to MODULE.bazel

Add aspect_rules_py as a dependency alongside your existing rules_python:
Check the releases page for the latest version.

Step 2: Update load statements

The minimal migration involves replacing load() statements in your BUILD files. The rule names stay the same, only the source changes:
The rule attributes, including 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 ancestor BUILD file of your Python code. They tell Gazelle to write aspect_rules_py load() statements instead of rules_python ones going forward:
Then run Gazelle to regenerate all affected BUILD files at once:

Step 4: Migrate to uv

This step is optional but recommended. uv replaces pip.parse as the dependency management layer, which gives you a single cross-platform lockfile, faster builds, and cross-platform container builds.

Before: pip.parse

After: uv

First, if you don’t already have a pyproject.toml, create one:
Then, import your existing dependencies using the lockfile as constraints so uv produces the same dependency solution you already have:
Then replace the pip.parse block in MODULE.bazel with:
Add a default venv to .bazelrc. Use the name from your pyproject.toml:
Package references in BUILD files change from @my_deps//... to @pypi//...:
Each [dependency-group] in your pyproject.toml becomes a named build configuration. pip.parse needed a separate requirements_lock.txt per Python version and couldn’t switch dependency sets at build time. With uv, you switch between configurations, such as production, dev, and testing, with a single flag:
If pyproject.toml declares no dependency groups, the uv integration creates an implicit configuration named after your project. For full setup and configuration details, see Manage dependencies with uv.

Features available after migrating

aspect_rules_py adds capabilities that rules_python doesn’t 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 in MODULE.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, use uv.override_package in MODULE.bazel:
Any target that depends on @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

pip.parse selects wheels for the host platform. On a Mac, that means macOS wheels, with compiled extensions linked against macOS system libraries, which don’t run when packaged into a Linux container. aspect_rules_py with uv selects wheels for the target platform, wherever the build runs. Declare a target platform, and Bazel fetches the Linux wheels and produces an image that runs on Linux from your Mac, without Docker installed.
For the full setup including ARM64 and libc version configuration, see Cross-platform builds in Manage dependencies with uv.

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, pip detects entrypoints, such as the command-line scripts installed by black, automatically at setup time. With uv, installs happen during the build, so declare entrypoints as Bazel targets (for example with py_console_script_binary) 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: