TIL: uv.override_requirement() in aspect_rules_py

2026/02/23

BUILDbazeldependencies

If you’re using aspect_rules_py with the uv extension to manage Python deps in Bazel, and you have a package that’s system-installed (not pip-installed) — like torch from an NVIDIA NGC Docker image — there’s a clean escape hatch I didn’t know about until today.

The Problem

In our monorepo, torch is gated to macOS in pyproject.toml (sys_platform == 'darwin') because on Linux CI it comes pre-installed in the Docker image. This means uv.lock has macOS wheels for torch but nothing for Linux. When Bazel generates the @pypi//torch target, the select() statement has no Linux match and you get:

no match for @platforms//os:linux in this configuration

62% of our Python packages depend on torch. That’s… a problem.

(The full story is in How Bazel Resolves Python Deps (and Why torch Breaks Everything).)

The Fix

aspect_rules_py (1.8.4+) has uv.override_requirement():

# MODULE.bazel
uv.override_requirement(
    hub_name = "pypi",
    venv_name = "default",
    requirement = "torch",
    target = "//third_party/torch:torch",  # your custom target
)

That //third_party/torch:torch can be whatever you want — a py_library wrapping system site-packages, a prebuilt wheel, an empty placeholder for CI. The point is: you control it.

How It Works

I went spelunking through extension.bzl to understand the mechanism. Three things happen:

  1. _parse_overrides() validates that the requirement actually exists in uv.lock. This is a sanity check — you can’t override a package Bazel doesn’t know about.

  2. When an override exists, it completely replaces the wheel install repo. No sdist builds, no whl downloads, no install steps. The entire pip machinery is bypassed.

  3. The swap happens in hub repo generation: overrides.get(package, _whl_install_repo_name(...)). If there’s an override, your custom target wins. If not, normal wheel resolution proceeds.

Clean and surgical. No monkey-patching, no wrapper hacks.

One Gotcha

The overridden package must still exist in uv.lock. Our torch works because there are macOS wheels in the lockfile — the validation passes. But packages like flash-attn or triton that are completely absent from the lockfile (e.g., sys_platform == 'never') can’t be overridden this way. Those need a different approach.

The Smoke Test

After wiring this up:

That’s it. One uv.override_requirement() call, and 62% of our packages stop failing resolution.