From 9042b6d4d6256b7eaabe3d957597f6b35fa9e163 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 1 Jan 2026 12:55:27 -0500 Subject: [PATCH] fix: don't renormalize extras Signed-off-by: Henry Schreiner --- src/packaging/markers.py | 14 +++++++++----- tests/test_markers.py | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/packaging/markers.py b/src/packaging/markers.py index 991a2484d..2ae53358c 100644 --- a/src/packaging/markers.py +++ b/src/packaging/markers.py @@ -211,7 +211,8 @@ def _normalize( # > compared using the semantics outlined in PEP 503 for names if key == "extra": assert isinstance(rhs, str), "extra value must be a string" - return (canonicalize_name(lhs), canonicalize_name(rhs)) + # Both sides are normalized at this point already + return (lhs, rhs) if key in MARKERS_ALLOWING_SET: if isinstance(rhs, str): # pragma: no cover return (canonicalize_name(lhs), canonicalize_name(rhs)) @@ -351,12 +352,15 @@ def evaluate( ) elif context == "metadata": current_environment["extra"] = "" + if environment is not None: current_environment.update(environment) - # The API used to allow setting extra to None. We need to handle this - # case for backwards compatibility. - if "extra" in current_environment and current_environment["extra"] is None: - current_environment["extra"] = "" + if "extra" in current_environment: + # The API used to allow setting extra to None. We need to handle + # this case for backwards compatibility. Also skip running + # normalize name if extra is empty. + extra = cast("str | None", current_environment["extra"]) + current_environment["extra"] = canonicalize_name(extra) if extra else "" return _evaluate_markers( self._markers, _repair_python_full_version(current_environment) diff --git a/tests/test_markers.py b/tests/test_markers.py index b4815c525..c7aa068aa 100644 --- a/tests/test_markers.py +++ b/tests/test_markers.py @@ -280,6 +280,13 @@ def test_environment_with_extra_none(self) -> None: # THEN assert marker.evaluate(environment) is False + def test_environment_with_no_extras(self) -> None: + # Environment is set but no 'extra' key is present; branch only hit on + # non-metadata context. + marker = Marker("os_name == 'foo'") + assert marker.evaluate({"os_name": "foo"}, context="requirement") + assert not marker.evaluate({"os_name": "bar"}, context="requirement") + @pytest.mark.parametrize( ("marker_string", "environment", "expected"), [