Skip to content

fix(markers): normalize extras/dependency_groups membership values at parse time#1310

Open
r266-tech wants to merge 1 commit into
pypa:mainfrom
r266-tech:fix/markers-plural-extras-normalize
Open

fix(markers): normalize extras/dependency_groups membership values at parse time#1310
r266-tech wants to merge 1 commit into
pypa:mainfrom
r266-tech:fix/markers-plural-extras-normalize

Conversation

@r266-tech

Copy link
Copy Markdown
Contributor

Problem

_normalize_extras (the parse-time normalizer run in Marker.__init__ / Requirement.__init__) canonicalizes only the singular extra == "X" form. The plural, set-valued membership forms "X" in extras and "X" in dependency_groups are left un-normalized at parse time, so a marker's __str__ / __eq__ / __hash__ disagree with its evaluate() result — and with PEP 685 (extras) / PEP 735 (dependency-groups), which mandate PEP 503 normalization of these names.

>>> from packaging.markers import Marker
>>> Marker('"Foo" in extras') == Marker('"foo" in extras')
False                              # str/hash differ ...
>>> Marker('"Foo" in extras').evaluate({"extras": {"foo"}})
True
>>> Marker('"foo" in extras').evaluate({"extras": {"foo"}})
True                               # ... yet they evaluate identically
>>> Marker('extra == "Foo"') == Marker('extra == "foo"')
True                               # the singular form is already normalized

evaluate() is consistent because _normalize canonicalizes both operands whenever key in MARKERS_ALLOWING_SET; only the parse-time path was missing the plural case. The practical consequence is that equal extras/dependency-groups markers fail to dedup in sets/dicts and compare unequal in requirement/lock-file processing.

Fix

Extend _normalize_extras to canonicalize the string-literal operand when the other operand is a Variable named extras/dependency_groups, mirroring the existing extra guards. The membership variable is always the right-hand operand (Value in Variable), so a single branch covers both in and not in. No operator check is needed because _normalize already normalizes these keys regardless of operator, so parse-time and eval-time now agree.

This is a follow-up completing the plural-membership gap left by the recent PEP 685 normalization work (#1246 nested singular extra, #1278 Requirement extras).

Tests

Added test_membership_value_str_normalization and ..._negated (parametrized over extras / dependency_groups) asserting str, ==, and hash consistency for both in and not in. Full tests/test_markers.py (2284 tests) passes; ruff check and ruff format are clean.

… parse time

_normalize_extras (run in Marker.__init__) canonicalized only the singular
'extra == "X"' form. The plural set-valued membership forms '"X" in extras'
and '"X" in dependency_groups' were left un-normalized at parse time, so
__str__/__eq__/__hash__ disagreed with evaluate() (whose _normalize canonicalizes
both operands for these keys) and with PEP 685 (extras) / PEP 735 (dependency
groups), which mandate PEP 503 normalization. Extend the parse-time normalizer to
canonicalize the membership literal, mirroring the existing extra guards, and add
regression tests for str/eq/hash consistency (both 'in' and 'not in').
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant