(common-deps-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"], )
The solution involves defining custom “platforms” mapped to separate dependency tracks inside MODULE.bazel. Using custom platforms via {obj}pip.default and associating requirements files to them through the requirements_by_platform attribute on {obj}pip.parse 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.