Skip to content

Commit 4be6df2

Browse files
committed
ensure Python 3.14 compatibility
1 parent 58b4878 commit 4be6df2

File tree

7 files changed

+72
-16
lines changed

7 files changed

+72
-16
lines changed

.github/workflows/tests.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jobs:
2525
- "3.11"
2626
- "3.12"
2727
- "3.13"
28+
- "3.14"
2829
include:
2930
- os: Ubuntu
3031
python-version: pypy-3.10

src/poetry/core/packages/utils/utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import functools
44
import re
55
import sys
6+
import warnings
67

78
from contextlib import suppress
89
from pathlib import Path
@@ -72,8 +73,23 @@ def url_to_path(url: str) -> Path:
7273
# According to RFC 8089, same as empty authority.
7374
netloc = ""
7475
elif netloc not in {".", ".."} and sys.platform == "win32":
75-
# If we have a UNC path, prepend UNC share notation.
76-
netloc = "\\\\" + netloc
76+
if re.match(r"^(localhost)?([a-zA-Z]:)$", netloc):
77+
path = "/" + netloc[-2:] + path
78+
if netloc.startswith("localhost"):
79+
message = (
80+
f"The file URL {url} is missing a slash between localhost and the drive letter."
81+
f" Did you mean file://localhost{path}?"
82+
)
83+
else:
84+
message = (
85+
f"The file URL {url} uses a drive letter without a leading slash."
86+
f" Did you mean file://{path}?"
87+
)
88+
warnings.warn(message, UserWarning, stacklevel=2)
89+
netloc = ""
90+
else:
91+
# If we have a UNC path, prepend UNC share notation.
92+
netloc = "\\\\" + netloc
7793
else:
7894
raise ValueError(
7995
f"non-local file URIs are not supported on this platform: {url}"

src/poetry/core/spdx/helpers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010

1111

1212
if TYPE_CHECKING:
13-
from importlib.abc import Traversable
13+
import sys
14+
15+
if sys.version_info < (3, 11):
16+
from importlib.abc import Traversable
17+
else:
18+
from importlib.resources.abc import Traversable
1419

1520

1621
def _get_license_file() -> Traversable:

tests/integration/test_pep517_backend.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
BUILD_SYSTEM_TEMPLATE = """
1616
[build-system]
17-
requires = ["poetry-core @ file://{project_path}"]
17+
requires = ["poetry-core @ {project_uri}"]
1818
build-backend = "poetry.core.masonry.api"
1919
"""
2020

@@ -41,9 +41,7 @@ def test_pip_install(
4141
with (temp_pep_517_backend_path / "pyproject.toml").open(
4242
mode="a", encoding="utf-8"
4343
) as f:
44-
f.write(
45-
BUILD_SYSTEM_TEMPLATE.format(project_path=project_source_root.as_posix())
46-
)
44+
f.write(BUILD_SYSTEM_TEMPLATE.format(project_uri=project_source_root.as_uri()))
4745

4846
subprocess_run(
4947
python,

tests/packages/test_directory_dependency.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import sys
4+
35
from pathlib import Path
46
from typing import TYPE_CHECKING
57
from typing import cast
@@ -94,7 +96,8 @@ def test_directory_dependency_pep_508_local_absolute() -> None:
9496
)
9597
expected = f"demo @ {path.as_uri()}"
9698

97-
requirement = f"demo @ file://{path.as_posix()}"
99+
prefix = "/" if sys.platform == "win32" else ""
100+
requirement = f"demo @ file://{prefix}{path.as_posix()}"
98101
_test_directory_dependency_pep_508("demo", path, requirement, expected)
99102

100103
requirement = f"demo @ {path}"
@@ -107,16 +110,17 @@ def test_directory_dependency_pep_508_localhost() -> None:
107110
/ "fixtures"
108111
/ "project_with_multi_constraints_dependency"
109112
)
110-
requirement = f"demo @ file://localhost{path.as_posix()}"
113+
prefix = "/" if sys.platform == "win32" else ""
114+
requirement = f"demo @ file://localhost{prefix}{path.as_posix()}"
111115
expected = f"demo @ {path.as_uri()}"
112116
_test_directory_dependency_pep_508("demo", path, requirement, expected)
113117

114118

115119
def test_directory_dependency_pep_508_local_relative() -> None:
116120
path = Path("..") / "fixtures" / "project_with_multi_constraints_dependency"
117121

122+
requirement = f"demo @ file://{path.as_posix()}"
118123
with pytest.raises(ValueError):
119-
requirement = f"demo @ file://{path.as_posix()}"
120124
_test_directory_dependency_pep_508("demo", path, requirement)
121125

122126
requirement = f"demo @ {path}"
@@ -133,7 +137,10 @@ def test_directory_dependency_pep_508_with_subdirectory() -> None:
133137
)
134138
expected = f"demo @ {path.as_uri()}"
135139

136-
requirement = f"demo @ file://{path.parent.as_posix()}#subdirectory={path.name}"
140+
prefix = "/" if sys.platform == "win32" else ""
141+
requirement = (
142+
f"demo @ file://{prefix}{path.parent.as_posix()}#subdirectory={path.name}"
143+
)
137144
_test_directory_dependency_pep_508("demo", path, requirement, expected)
138145

139146

@@ -143,7 +150,8 @@ def test_directory_dependency_pep_508_extras() -> None:
143150
/ "fixtures"
144151
/ "project_with_multi_constraints_dependency"
145152
)
146-
requirement = f"demo[foo,bar] @ file://{path.as_posix()}"
153+
prefix = "/" if sys.platform == "win32" else ""
154+
requirement = f"demo[foo,bar] @ file://{prefix}{path.as_posix()}"
147155
expected = f"demo[bar,foo] @ {path.as_uri()}"
148156
_test_directory_dependency_pep_508("demo", path, requirement, expected)
149157

@@ -154,7 +162,8 @@ def test_directory_dependency_pep_508_with_marker() -> None:
154162
/ "fixtures"
155163
/ "project_with_multi_constraints_dependency"
156164
)
157-
requirement = f'demo @ file://{path.as_posix()} ; sys_platform == "linux"'
165+
prefix = "/" if sys.platform == "win32" else ""
166+
requirement = f'demo @ file://{prefix}{path.as_posix()} ; sys_platform == "linux"'
158167
expected = f'demo @ {path.as_uri()} ; sys_platform == "linux"'
159168
_test_directory_dependency_pep_508("demo", path, requirement, expected)
160169

tests/packages/test_file_dependency.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import sys
4+
35
from pathlib import Path
46
from typing import TYPE_CHECKING
57
from typing import cast
@@ -83,7 +85,8 @@ def test_file_dependency_pep_508_local_file_absolute(mocker: MockerFixture) -> N
8385
path = DIST_PATH / "demo-0.2.0.tar.gz"
8486
expected = f"demo @ {path.as_uri()}"
8587

86-
requirement = f"demo @ file://{path.as_posix()}"
88+
prefix = "/" if sys.platform == "win32" else ""
89+
requirement = f"demo @ file://{prefix}{path.as_posix()}"
8790
_test_file_dependency_pep_508(mocker, "demo", path, requirement, expected)
8891

8992
requirement = f"demo @ {path}"
@@ -92,7 +95,8 @@ def test_file_dependency_pep_508_local_file_absolute(mocker: MockerFixture) -> N
9295

9396
def test_file_dependency_pep_508_local_file_localhost(mocker: MockerFixture) -> None:
9497
path = DIST_PATH / "demo-0.2.0.tar.gz"
95-
requirement = f"demo @ file://localhost{path.as_posix()}"
98+
prefix = "/" if sys.platform == "win32" else ""
99+
requirement = f"demo @ file://localhost{prefix}{path.as_posix()}"
96100
expected = f"demo @ {path.as_uri()}"
97101
_test_file_dependency_pep_508(mocker, "demo", path, requirement, expected)
98102

@@ -116,7 +120,8 @@ def test_file_dependency_pep_508_with_subdirectory(mocker: MockerFixture) -> Non
116120
path = DIST_PATH / "demo.zip"
117121
expected = f"demo @ {path.as_uri()}#subdirectory=sub"
118122

119-
requirement = f"demo @ file://{path.as_posix()}#subdirectory=sub"
123+
prefix = "/" if sys.platform == "win32" else ""
124+
requirement = f"demo @ file://{prefix}{path.as_posix()}#subdirectory=sub"
120125
_test_file_dependency_pep_508(mocker, "demo", path, requirement, expected)
121126

122127

tests/packages/utils/test_utils_urls.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,28 @@ def test_url_to_path(url: str, win_expected: str, non_win_expected: str | None)
5555
assert url_to_path(url) == Path(expected_path)
5656

5757

58+
@pytest.mark.skipif("sys.platform != 'win32'")
59+
def test_url_to_path_deprecated() -> None:
60+
# This is actually not valid, but we keep it for backwards-compatibility.
61+
with pytest.warns(UserWarning) as e:
62+
assert url_to_path("file://c:/tmp/file") == Path(r"C:\tmp\file")
63+
assert str(e[0].message) == (
64+
"The file URL file://c:/tmp/file uses a drive letter without a leading slash."
65+
" Did you mean file:///c:/tmp/file?"
66+
)
67+
68+
69+
@pytest.mark.skipif("sys.platform != 'win32'")
70+
def test_url_to_path_deprecated_localhost() -> None:
71+
# This is actually not valid, but we keep it for backwards-compatibility.
72+
with pytest.warns(UserWarning) as e:
73+
assert url_to_path("file://localhostc:/tmp/file") == Path(r"C:\tmp\file")
74+
assert str(e[0].message) == (
75+
"The file URL file://localhostc:/tmp/file is missing a slash between localhost"
76+
" and the drive letter. Did you mean file://localhost/c:/tmp/file?"
77+
)
78+
79+
5880
@pytest.mark.skipif("sys.platform != 'win32'")
5981
def test_url_to_path_path_to_url_symmetry_win() -> None:
6082
path = r"C:\tmp\file"

0 commit comments

Comments
 (0)