> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from rules_python

> Migrate Bazel Python projects from rules_python to aspect_rules_py: drop-in py_binary, py_library, py_test rules plus optional uv for dependencies.

`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 legacy `rules_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**](https://github.com/aspect-build/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

Add `aspect_rules_py` as a dependency alongside your existing `rules_python`:

```python theme={null}
# MODULE.bazel
bazel_dep(name = "aspect_rules_py") # add this
```

## 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:

```python theme={null}
# Before
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

# After
load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_library", "py_test")
```

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](https://github.com/aspect-build/aspect-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:

```python theme={null}
# 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
```

Then run Gazelle to regenerate all affected BUILD files at once:

```bash theme={null}
aspect gazelle
```

## 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

```python theme={null}
# MODULE.bazel
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
    hub_name = "my_deps",
    python_version = "3.11",
    requirements_lock = "//:requirements_lock_3_11.txt",
)
use_repo(pip, "my_deps")
```

### After: uv

First, if you don't already have a `pyproject.toml`, create one:

```bash theme={null}
uv init --no-workspace
```

Then, import your existing dependencies using the lockfile as constraints so uv produces the same dependency solution you already have:

```bash theme={null}
uv add -r requirements.txt -c requirements_lock.txt
```

Then replace the `pip.parse` block in `MODULE.bazel` with:

```python theme={null}
# MODULE.bazel
uv = use_extension("@aspect_rules_py//uv/unstable:extension.bzl", "uv")
uv.declare_hub(hub_name = "pypi")
uv.project(
    hub_name = "pypi",
    pyproject = "//:pyproject.toml",
    lock = "//:uv.lock",
)
use_repo(uv, "pypi")
```

Add a default venv to `.bazelrc`. Use the `name` from your `pyproject.toml`:

```bash theme={null}
# .bazelrc
common --@pypi//venv=myproject
```

Package references in BUILD files change from `@my_deps//...` to `@pypi//...`:

```python theme={null}
# Before
deps = ["@my_deps//requests"]

# After
deps = ["@pypi//requests"]
```

Each [`[dependency-group]`](https://docs.astral.sh/uv/concepts/dependencies/#dependency-groups) 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:

```bash theme={null}
bazel test //... --@pypi//venv=dev
```

Aspect's uv integration also creates an implicit default configuration named after your project, so you don't need to define one explicitly for the common case.

For full setup and configuration details, see [Manage dependencies with uv](/docs/bazel/python/uv).

## 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 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.

```python theme={null}
py_binary(
    name = "app",
    srcs = ["say.py"],
    python_version = "3.9",   # This target uses 3.9
    deps = ["@pypi//cowsay"],
)

py_test(
    name = "app_test",
    srcs = ["app_test.py"],
    python_version = "3.9",
    deps = ["@pypi//pytest"],
)
```

### 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`:

```python theme={null}
# MODULE.bazel
uv.override_package(
    lock = "//:uv.lock",
    name = "cowsay",
    target = "//third_party/py/cowsay:cowsay",
)
```

Any target that depends on `@pypi//cowsay` uses your local version instead of the one from PyPI. See [Swapping in a local package](/docs/bazel/python/uv#swapping-in-a-local-package) in the uv guide for details.

### Cross-platform container builds

**A rules\_python limitation**
`rules_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.

```python theme={null}
platform(
    name = "amd64_linux",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
)
```

```bash theme={null}
bazel build //app:image --platforms=:amd64_linux
```

For the full setup including ARM64 and libc version configuration, see [Cross-platform builds](/docs/bazel/python/uv#cross-platform-builds) in [Manage dependencies with uv](/docs/bazel/python/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`, 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:

```toml theme={null}
[project]
dependencies = [
  "setuptools",
  "build",
  # ... your other deps
]
```
