Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/13510.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix normalization of local link on Windows in newer Python versions.
5 changes: 2 additions & 3 deletions src/pip/_internal/models/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import os
import posixpath
import re
import sys
import urllib.parse
from collections.abc import Mapping
from dataclasses import dataclass
Expand Down Expand Up @@ -133,8 +132,8 @@ def _clean_file_url_path(part: str) -> str:
# exist, the colon should be quoted. We rely on urllib.request
# to do the right thing here.
ret = urllib.request.pathname2url(urllib.request.url2pathname(part))
if sys.version_info >= (3, 14):
# https://discuss.python.org/t/pathname2url-changes-in-python-3-14-breaking-pip-tests/97091
if ret.startswith("///"):
# Remove any URL authority section, leaving only the URL path.
ret = ret.removeprefix("//")
return ret

Expand Down
19 changes: 1 addition & 18 deletions tests/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from io import BytesIO, StringIO
from textwrap import dedent
from typing import Any, AnyStr, Callable, Literal, Protocol, Union, cast
from urllib.parse import urlparse, urlunparse
from urllib.request import pathname2url
from zipfile import ZipFile

Expand Down Expand Up @@ -1370,25 +1369,9 @@ def __call__(
# Accommodations for Windows path and URL changes in recent Python releases
# -------------------------------------------------------------------------

# versions containing fix/backport from https://github.com/python/cpython/pull/113563
# which changed the behavior of `urllib.parse.urlun{parse,split}`
url = "////path/to/file"
has_new_urlun_behavior = url == urlunparse(urlparse(url))

# the above change seems to only impact tests on Windows, so just add skips for that
skip_needs_new_urlun_behavior_win = pytest.mark.skipif(
sys.platform != "win32" or not has_new_urlun_behavior,
reason="testing windows behavior for newer CPython",
)

skip_needs_old_urlun_behavior_win = pytest.mark.skipif(
sys.platform != "win32" or has_new_urlun_behavior,
reason="testing windows behavior for older CPython",
)

# Trailing slashes are now preserved on Windows, matching POSIX behaviour.
# BPO: https://github.com/python/cpython/issues/126212
does_pathname2url_preserve_trailing_slash = pathname2url("C:/foo/").endswith("/")
does_pathname2url_preserve_trailing_slash = pathname2url("C:\\foo\\").endswith("/")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This detection didn't work correctly, but I suspect it didn't matter because the has_new_urlun_behavior value coincided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(took me flippin ages to figure this out!)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using raw strings for paths to have the backslash character be a literal character?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't help us much here:

>>> r"C:\foo\"
  File "<stdin>", line 1
    r"C:\foo\"
    ^
SyntaxError: unterminated string literal (detected at line 1)

skip_needs_new_pathname2url_trailing_slash_behavior_win = pytest.mark.skipif(
sys.platform != "win32" or not does_pathname2url_preserve_trailing_slash,
reason="testing windows (pathname2url) behavior for newer CPython",
Expand Down
31 changes: 5 additions & 26 deletions tests/unit/test_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@
TestData,
make_test_link_collector,
skip_needs_new_pathname2url_trailing_slash_behavior_win,
skip_needs_new_urlun_behavior_win,
skip_needs_old_pathname2url_trailing_slash_behavior_win,
skip_needs_old_urlun_behavior_win,
)

ACCEPT = ", ".join(
Expand Down Expand Up @@ -309,7 +307,7 @@ def test_clean_url_path(path: str, expected: str, is_local_path: bool) -> None:
# Test a VCS path with a Windows drive letter and revision.
pytest.param(
"/T:/with space/repo.git@1.0",
"///T:/with%20space/repo.git@1.0",
"/T:/with%20space/repo.git@1.0",
marks=pytest.mark.skipif("sys.platform != 'win32'"),
),
# Test a VCS path with a Windows drive letter and revision,
Expand Down Expand Up @@ -393,26 +391,12 @@ def test_clean_url_path_with_local_path(path: str, expected: str) -> None:
pytest.param(
"file:///T:/path/with spaces/",
"file:///T:/path/with%20spaces",
marks=[
skip_needs_old_urlun_behavior_win,
skip_needs_old_pathname2url_trailing_slash_behavior_win,
],
),
pytest.param(
"file:///T:/path/with spaces/",
"file://///T:/path/with%20spaces",
marks=[
skip_needs_new_urlun_behavior_win,
skip_needs_old_pathname2url_trailing_slash_behavior_win,
],
marks=skip_needs_old_pathname2url_trailing_slash_behavior_win,
),
pytest.param(
"file:///T:/path/with spaces/",
"file://///T:/path/with%20spaces/",
marks=[
skip_needs_new_urlun_behavior_win,
skip_needs_new_pathname2url_trailing_slash_behavior_win,
],
"file:///T:/path/with%20spaces/",
marks=skip_needs_new_pathname2url_trailing_slash_behavior_win,
),
# URL with Windows drive letter, running on non-windows
# platform. The `:` after the drive should be quoted.
Expand All @@ -425,12 +409,7 @@ def test_clean_url_path_with_local_path(path: str, expected: str) -> None:
pytest.param(
"git+file:///T:/with space/repo.git@1.0#egg=my-package-1.0",
"git+file:///T:/with%20space/repo.git@1.0#egg=my-package-1.0",
marks=skip_needs_old_urlun_behavior_win,
),
pytest.param(
"git+file:///T:/with space/repo.git@1.0#egg=my-package-1.0",
"git+file://///T:/with%20space/repo.git@1.0#egg=my-package-1.0",
marks=skip_needs_new_urlun_behavior_win,
marks=pytest.mark.skipif("sys.platform != 'win32'"),
),
# Test a VCS URL with a Windows drive letter and revision,
# running on non-windows platform.
Expand Down
Loading