Skip to content

Commit 2966a3e

Browse files
authored
Merge pull request #12044 from chriskuehl/fix-pep658-name-normalization
Canonicalize package names before comparison for PEP658 metadata
2 parents 335e114 + 767bb40 commit 2966a3e

File tree

3 files changed

+68
-3
lines changed

3 files changed

+68
-3
lines changed

news/12038.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix installation of packages with PEP658 metadata using non-canonicalized names

src/pip/_internal/operations/prepare.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def _fetch_metadata_using_link_data_attr(
410410
# NB: raw_name will fall back to the name from the install requirement if
411411
# the Name: field is not present, but it's noted in the raw_name docstring
412412
# that that should NEVER happen anyway.
413-
if metadata_dist.raw_name != req.req.name:
413+
if canonicalize_name(metadata_dist.raw_name) != canonicalize_name(req.req.name):
414414
raise MetadataInconsistent(
415415
req, "Name", req.req.name, metadata_dist.raw_name
416416
)

tests/functional/test_download.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from hashlib import sha256
99
from pathlib import Path
1010
from textwrap import dedent
11-
from typing import Callable, Dict, List, Tuple
11+
from typing import Callable, Dict, List, Optional, Tuple
1212

1313
import pytest
1414

@@ -1266,6 +1266,8 @@ class Package:
12661266
metadata: MetadataKind
12671267
# This will override any dependencies specified in the actual dist's METADATA.
12681268
requires_dist: Tuple[str, ...] = ()
1269+
# This will override the Name specified in the actual dist's METADATA.
1270+
metadata_name: Optional[str] = None
12691271

12701272
def metadata_filename(self) -> str:
12711273
"""This is specified by PEP 658."""
@@ -1296,7 +1298,7 @@ def generate_metadata(self) -> bytes:
12961298
return dedent(
12971299
f"""\
12981300
Metadata-Version: 2.1
1299-
Name: {self.name}
1301+
Name: {self.metadata_name or self.name}
13001302
Version: {self.version}
13011303
{self.requires_str()}
13021304
"""
@@ -1452,6 +1454,14 @@ def run_for_generated_index(
14521454
),
14531455
# This will raise an error when pip attempts to fetch the metadata file.
14541456
Package("simple2", "2.0", "simple2-2.0.tar.gz", MetadataKind.NoFile),
1457+
# This has a METADATA file with a mismatched name.
1458+
Package(
1459+
"simple2",
1460+
"3.0",
1461+
"simple2-3.0.tar.gz",
1462+
MetadataKind.Sha256,
1463+
metadata_name="not-simple2",
1464+
),
14551465
],
14561466
"colander": [
14571467
# Ensure we can read the dependencies from a metadata file within a wheel
@@ -1491,6 +1501,16 @@ def run_for_generated_index(
14911501
"priority", "1.0", "priority-1.0-py2.py3-none-any.whl", MetadataKind.NoFile
14921502
),
14931503
],
1504+
"requires-simple-extra": [
1505+
# Metadata name is not canonicalized.
1506+
Package(
1507+
"requires-simple-extra",
1508+
"0.1",
1509+
"requires_simple_extra-0.1-py2.py3-none-any.whl",
1510+
MetadataKind.Sha256,
1511+
metadata_name="Requires_Simple.Extra",
1512+
),
1513+
],
14941514
}
14951515

14961516

@@ -1581,3 +1601,47 @@ def test_metadata_not_found(
15811601
f"ERROR: 404 Client Error: FileNotFoundError for url:.*{expected_re}"
15821602
)
15831603
assert pattern.search(result.stderr), (pattern, result.stderr)
1604+
1605+
1606+
def test_produces_error_for_mismatched_package_name_in_metadata(
1607+
download_generated_html_index: Callable[..., Tuple[TestPipResult, Path]],
1608+
) -> None:
1609+
"""Verify that the package name from the metadata matches the requested package."""
1610+
result, _ = download_generated_html_index(
1611+
_simple_packages,
1612+
["simple2==3.0"],
1613+
allow_error=True,
1614+
)
1615+
assert result.returncode != 0
1616+
assert (
1617+
"simple2-3.0.tar.gz has inconsistent Name: expected 'simple2', but metadata "
1618+
"has 'not-simple2'"
1619+
) in result.stdout
1620+
1621+
1622+
@pytest.mark.parametrize(
1623+
"requirement",
1624+
(
1625+
"requires-simple-extra==0.1",
1626+
"REQUIRES_SIMPLE-EXTRA==0.1",
1627+
"REQUIRES....simple-_-EXTRA==0.1",
1628+
),
1629+
)
1630+
def test_canonicalizes_package_name_before_verifying_metadata(
1631+
download_generated_html_index: Callable[..., Tuple[TestPipResult, Path]],
1632+
requirement: str,
1633+
) -> None:
1634+
"""Verify that the package name from the command line and the package's
1635+
METADATA are both canonicalized before comparison.
1636+
1637+
Regression test for https://github.com/pypa/pip/issues/12038
1638+
"""
1639+
result, download_dir = download_generated_html_index(
1640+
_simple_packages,
1641+
[requirement],
1642+
allow_error=True,
1643+
)
1644+
assert result.returncode == 0
1645+
assert os.listdir(download_dir) == [
1646+
"requires_simple_extra-0.1-py2.py3-none-any.whl",
1647+
]

0 commit comments

Comments
 (0)