How to use a common set of dependencies with multiple PyPI versions

In this guide, we show how to handle a situation common to monorepos that extensively share code: How do multiple binaries utilize distinct requirements files while pulling from shared internal libraries, without requiring manually-maintained select() logic inside dependency targets?

Stated as code, consider this example:

# When building bin_alpha, requests and more_itertools should resolve
# from requirements_alpha.txt
py_binary(
    name = "bin_alpha",
    deps = [
        "@pypi//requests",
        ":common",
    ],
)

# When building bin_beta, requests and more_itertools should resolve
# from requirements_beta.txt
py_binary(
    name = "bin_beta",
    deps = [
        "@pypi//requests",
        ":common",
    ],
)

# Transitive dependencies like more_itertools are requested here, but
# must automatically match whichever dependency track is active for the binary.
py_library(
    name = "common",
    deps = ["@pypi//more_itertools"],
)

Defining dependency tracks via custom platforms

The solution involves defining custom “platforms” mapped to separate dependency tracks inside MODULE.bazel. Using custom platforms via and associating requirements files to them through the requirements_by_platform attribute on instructs rules_python to generate select() logic behind a unified hub.

Binaries configure their execution requirements by forcing flag transition attributes using custom build setting flags.

In this example, we define custom string flag named //:pypi_hub, setup distinct custom platforms for "alpha" and "beta" profiles, and register associated requirements lock files grouped inside the @pypi hub.

# File: MODULE.bazel

rules_python_config = use_extension(
    "@rules_python//python/extensions:config.bzl",
    "config",
)
rules_python_config.add_transition_setting(
    setting = "//:pypi_hub",
)

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")

pip.default(
    platform = "alpha",
    config_settings = ["@//:is_pypi_alpha"],
)

pip.default(
    platform = "beta",
    config_settings = ["@//:is_pypi_beta"],
)

pip.parse(
    hub_name = "pypi",
    python_version = "3.14",
    requirements_by_platform = {
        "//:requirements_alpha.txt": "alpha",
        "//:requirements_beta.txt": "beta",
    },
)

use_repo(pip, "pypi")
# File: BUILD.bazel

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")

string_flag(
    name = "pypi_hub",
    build_setting_default = "none",
)

config_setting(
    name = "is_pypi_alpha",
    flag_values = {"//:pypi_hub": "alpha"},
)

config_setting(
    name = "is_pypi_beta",
    flag_values = {"//:pypi_hub": "beta"},
)

py_binary(
    name = "bin_alpha",
    srcs = ["bin_alpha.py"],
    config_settings = {
        "//:pypi_hub": "alpha",
    },
    deps = [
        "@pypi//requests",
        ":common",
    ],
)

py_binary(
    name = "bin_beta",
    srcs = ["bin_beta.py"],
    config_settings = {
        "//:pypi_hub": "beta",
    },
    deps = [
        "@pypi//requests",
        ":common",
    ],
)

py_library(
    name = "common",
    deps = ["@pypi//more_itertools"],
)

When building bin_alpha or bin_beta, they set //:pypi_hub via target transitions. The generated aliased dependencies inside the @pypi hub will evaluate that Bazel configuration, automatically delivering corresponding Python wheels from targeted lock files.