Pipstar extras not matched due to hyphen vs underscore normalization
🐞 bug report
Affected Rule
pip.parse / whl_library (Pipstar extras resolution)
Is this a regression?
Yes, this worked in rules_python 1.6.3 (non-Pipstar path). The extras fix in #3468 correctly passes extras down the call stack, but a normalization mismatch prevents them from being matched against Requires-Dist markers.
Description
PR #3468 fixed the issue of extras not being passed down in Pipstar. However, extras containing hyphens in requirements.txt are not normalized to underscores before being matched against the wheel's Requires-Dist metadata, which uses the PEP 685-normalized form (underscores).
For example, with sqlalchemy[asyncio,postgresql-asyncpg,postgresql-psycopg2binary]==2.0.36 in requirements.txt, the generated BUILD.bazel for the sqlalchemy whl_library shows:
extras = [ "asyncio", "postgresql-asyncpg", # hyphens from requirements.txt "postgresql-psycopg2binary", ], requires_dist = [ # ... "asyncpg ; extra == 'postgresql_asyncpg'", # underscores in wheel metadata "psycopg2-binary ; extra == 'postgresql_psycopg2binary'", # underscores in wheel metadata # ... ],
The extras list uses hyphens (postgresql-asyncpg) while Requires-Dist markers use underscores (postgresql_asyncpg). The comparison fails silently, so psycopg2-binary and asyncpg are never added as transitive dependencies of sqlalchemy.
Note that asyncio (no hyphens) works correctly because it doesn't need normalization.
Workaround: Use underscores instead of hyphens in the extras specifier in requirements.in:
sqlalchemy[asyncio,postgresql_psycopg2binary,postgresql_asyncpg]==2.0.36
🔬 Minimal Reproduction
requirements.in:
sqlalchemy[asyncio,postgresql-psycopg2binary,postgresql-asyncpg]==2.0.36
psycopg2-binary~=2.9.9
asyncpg~=0.30.0
MODULE.bazel:
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") pip.parse( hub_name = "pypi", python_version = "3.11", requirements_lock = "//:requirements.txt", ) use_repo(pip, "pypi")
Then query:
bazel query 'kind("py_library", deps(@pypi//sqlalchemy))' | grep -E 'psycopg|asyncpg' # Returns empty — psycopg2_binary and asyncpg are missing
On rules_python 1.6.3 (non-Pipstar), the same query correctly returns:
@pypi//psycopg2_binary:pkg
@pypi//asyncpg:pkg
🔥 Exception or Error
No error at build time. At runtime, tests fail with:
ModuleNotFoundError: No module named 'psycopg2'
🌍 Your Environment
Operating System:
macOS 15.3.1 (darwin 25.2.0, arm64)
Output of bazel version:
Rules_python version:
Anything else relevant?
The root cause appears to be that extras parsed from the requirement string in whl_library.bzl (via requirement(rctx.attr.requirement).extras) preserves the original hyphenated form from requirements.txt, but the Requires-Dist markers in wheel METADATA use PEP 685-normalized names (hyphens → underscores). The matching logic needs to normalize extras before comparison.