Skip to content

Commit 11d2a6f

Browse files
authored
Merge branch 'main' into feature/no-download-for-dry-run
2 parents fc2eb8d + ed05518 commit 11d2a6f

File tree

24 files changed

+384
-109
lines changed

24 files changed

+384
-109
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ jobs:
201201
python-version: ${{ matrix.python }}
202202
allow-prereleases: true
203203

204+
- name: Install SVN via Chocolatey
205+
shell: pwsh
206+
run: |
207+
choco install svn -y --no-progress
208+
echo "C:\Program Files (x86)\Subversion\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
209+
204210
- run: pip install nox
205211

206212
# Main check

news/13550.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
For Python versions that do not support PEP 706, pip will now raise an installation error for a
2+
source distribution when it includes a symlink that points outside the source distribution archive.

news/13581.removal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove support for non-standard legacy wheel filenames.

src/pip/_internal/build_env.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,14 @@ def install(
169169
args.append("--prefer-binary")
170170
args.append("--")
171171
args.extend(requirements)
172+
173+
identify_requirement = (
174+
f" for {for_req.name}" if for_req and for_req.name else ""
175+
)
172176
with open_spinner(f"Installing {kind}") as spinner:
173177
call_subprocess(
174178
args,
175-
command_desc=f"pip subprocess to install {kind}",
179+
command_desc=f"installing {kind}{identify_requirement}",
176180
spinner=spinner,
177181
)
178182

src/pip/_internal/cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def get(
143143
wheel = Wheel(wheel_name)
144144
except InvalidWheelFilename:
145145
continue
146-
if canonicalize_name(wheel.name) != canonical_package_name:
146+
if wheel.name != canonical_package_name:
147147
logger.debug(
148148
"Ignoring cached wheel %s for %s as it "
149149
"does not match the expected distribution name %s.",

src/pip/_internal/exceptions.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,23 @@ class InstallationError(PipError):
190190
"""General exception during installation"""
191191

192192

193+
class FailedToPrepareCandidate(InstallationError):
194+
"""Raised when we fail to prepare a candidate (i.e. fetch and generate metadata).
195+
196+
This is intentionally not a diagnostic error, since the output will be presented
197+
above this error, when this occurs. This should instead present information to the
198+
user.
199+
"""
200+
201+
def __init__(
202+
self, *, package_name: str, requirement_chain: str, failed_step: str
203+
) -> None:
204+
super().__init__(f"Failed to build '{package_name}' when {failed_step.lower()}")
205+
self.package_name = package_name
206+
self.requirement_chain = requirement_chain
207+
self.failed_step = failed_step
208+
209+
193210
class MissingPyProjectBuildRequires(DiagnosticPipError):
194211
"""Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
195212

@@ -384,7 +401,7 @@ def __init__(
384401
output_lines: list[str] | None,
385402
) -> None:
386403
if output_lines is None:
387-
output_prompt = Text("See above for output.")
404+
output_prompt = Text("No available output.")
388405
else:
389406
output_prompt = (
390407
Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
@@ -412,15 +429,15 @@ def __str__(self) -> str:
412429
return f"{self.command_description} exited with {self.exit_code}"
413430

414431

415-
class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
432+
class MetadataGenerationFailed(DiagnosticPipError, InstallationError):
416433
reference = "metadata-generation-failed"
417434

418435
def __init__(
419436
self,
420437
*,
421438
package_details: str,
422439
) -> None:
423-
super(InstallationSubprocessError, self).__init__(
440+
super().__init__(
424441
message="Encountered error while generating package metadata.",
425442
context=escape(package_details),
426443
hint_stmt="See above for details.",

src/pip/_internal/index/package_finder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from pip._vendor.packaging import specifiers
1919
from pip._vendor.packaging.tags import Tag
20-
from pip._vendor.packaging.utils import canonicalize_name
20+
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
2121
from pip._vendor.packaging.version import InvalidVersion, _BaseVersion
2222
from pip._vendor.packaging.version import parse as parse_version
2323

@@ -127,7 +127,7 @@ class LinkEvaluator:
127127
def __init__(
128128
self,
129129
project_name: str,
130-
canonical_name: str,
130+
canonical_name: NormalizedName,
131131
formats: frozenset[str],
132132
target_python: TargetPython,
133133
allow_yanked: bool,
@@ -201,7 +201,7 @@ def evaluate_link(self, link: Link) -> tuple[LinkType, str]:
201201
LinkType.format_invalid,
202202
"invalid wheel filename",
203203
)
204-
if canonicalize_name(wheel.name) != self._canonical_name:
204+
if wheel.name != self._canonical_name:
205205
reason = f"wrong project name (not {self.project_name})"
206206
return (LinkType.different_project, reason)
207207

src/pip/_internal/metadata/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import functools
55
import os
66
import sys
7-
from typing import Literal, Protocol, cast
7+
from typing import TYPE_CHECKING, Literal, Protocol, cast
88

99
from pip._internal.utils.deprecation import deprecated
1010
from pip._internal.utils.misc import strtobool
1111

1212
from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
1313

14+
if TYPE_CHECKING:
15+
from pip._vendor.packaging.utils import NormalizedName
16+
1417
__all__ = [
1518
"BaseDistribution",
1619
"BaseEnvironment",
@@ -131,7 +134,9 @@ def get_directory_distribution(directory: str) -> BaseDistribution:
131134
return select_backend().Distribution.from_directory(directory)
132135

133136

134-
def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution:
137+
def get_wheel_distribution(
138+
wheel: Wheel, canonical_name: NormalizedName
139+
) -> BaseDistribution:
135140
"""Get the representation of the specified wheel's distribution metadata.
136141
137142
This returns a Distribution instance from the chosen backend based on

src/pip/_internal/models/wheel.py

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,91 +4,30 @@
44

55
from __future__ import annotations
66

7-
import re
87
from collections.abc import Iterable
98

109
from pip._vendor.packaging.tags import Tag
11-
from pip._vendor.packaging.utils import BuildTag, parse_wheel_filename
1210
from pip._vendor.packaging.utils import (
1311
InvalidWheelFilename as _PackagingInvalidWheelFilename,
1412
)
13+
from pip._vendor.packaging.utils import parse_wheel_filename
1514

1615
from pip._internal.exceptions import InvalidWheelFilename
17-
from pip._internal.utils.deprecation import deprecated
1816

1917

2018
class Wheel:
2119
"""A wheel file"""
2220

23-
legacy_wheel_file_re = re.compile(
24-
r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]*?))
25-
((-(?P<build>\d[^-]*?))?-(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>[^\s-]+?)
26-
\.whl|\.dist-info)$""",
27-
re.VERBOSE,
28-
)
29-
3021
def __init__(self, filename: str) -> None:
3122
self.filename = filename
3223

33-
# To make mypy happy specify type hints that can come from either
34-
# parse_wheel_filename or the legacy_wheel_file_re match.
35-
self.name: str
36-
self._build_tag: BuildTag | None = None
37-
3824
try:
3925
wheel_info = parse_wheel_filename(filename)
40-
self.name, _version, self._build_tag, self.file_tags = wheel_info
41-
self.version = str(_version)
4226
except _PackagingInvalidWheelFilename as e:
43-
# Check if the wheel filename is in the legacy format
44-
legacy_wheel_info = self.legacy_wheel_file_re.match(filename)
45-
if not legacy_wheel_info:
46-
raise InvalidWheelFilename(e.args[0]) from None
47-
48-
deprecated(
49-
reason=(
50-
f"Wheel filename {filename!r} is not correctly normalised. "
51-
"Future versions of pip will raise the following error:\n"
52-
f"{e.args[0]}\n\n"
53-
),
54-
replacement=(
55-
"to rename the wheel to use a correctly normalised "
56-
"name (this may require updating the version in "
57-
"the project metadata)"
58-
),
59-
gone_in="25.3",
60-
issue=12938,
61-
)
62-
63-
self.name = legacy_wheel_info.group("name").replace("_", "-")
64-
self.version = legacy_wheel_info.group("ver").replace("_", "-")
65-
66-
# Generate the file tags from the legacy wheel filename
67-
pyversions = legacy_wheel_info.group("pyver").split(".")
68-
abis = legacy_wheel_info.group("abi").split(".")
69-
plats = legacy_wheel_info.group("plat").split(".")
70-
self.file_tags = frozenset(
71-
Tag(interpreter=py, abi=abi, platform=plat)
72-
for py in pyversions
73-
for abi in abis
74-
for plat in plats
75-
)
76-
77-
@property
78-
def build_tag(self) -> BuildTag:
79-
if self._build_tag is not None:
80-
return self._build_tag
81-
82-
# Parse the build tag from the legacy wheel filename
83-
legacy_wheel_info = self.legacy_wheel_file_re.match(self.filename)
84-
assert legacy_wheel_info is not None, "guaranteed by filename validation"
85-
build_tag = legacy_wheel_info.group("build")
86-
match = re.match(r"^(\d+)(.*)$", build_tag)
87-
assert match is not None, "guaranteed by filename validation"
88-
build_tag_groups = match.groups()
89-
self._build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
90-
91-
return self._build_tag
27+
raise InvalidWheelFilename(e.args[0]) from None
28+
29+
self.name, _version, self.build_tag, self.file_tags = wheel_info
30+
self.version = str(_version)
9231

9332
def get_formatted_file_tags(self) -> list[str]:
9433
"""Return the wheel's tags as a sorted list of strings."""

src/pip/_internal/network/lazy_wheel.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Any
1212
from zipfile import BadZipFile, ZipFile
1313

14-
from pip._vendor.packaging.utils import canonicalize_name
14+
from pip._vendor.packaging.utils import NormalizedName
1515
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
1616

1717
from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
@@ -23,7 +23,9 @@ class HTTPRangeRequestUnsupported(Exception):
2323
pass
2424

2525

26-
def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
26+
def dist_from_wheel_url(
27+
name: NormalizedName, url: str, session: PipSession
28+
) -> BaseDistribution:
2729
"""Return a distribution object from the given wheel URL.
2830
2931
This uses HTTP range requests to only fetch the portion of the wheel
@@ -37,7 +39,7 @@ def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistrib
3739
wheel = MemoryWheel(zf.name, zf) # type: ignore
3840
# After context manager exit, wheel.name
3941
# is an invalid file by intention.
40-
return get_wheel_distribution(wheel, canonicalize_name(name))
42+
return get_wheel_distribution(wheel, name)
4143

4244

4345
class LazyZipOverHTTP:

0 commit comments

Comments
 (0)