Skip to content

Commit 78f18e1

Browse files
authored
Merge pull request #2224 from sirosen/fix-url-normalization
Bugfix: path normalization applied to URLs
2 parents 6e0ef33 + 1658759 commit 78f18e1

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

changelog.d/2223.bugfix.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed a bug which removed slashes from URLs in ``-r`` and ``-c`` in the output
2+
of ``pip-compile`` -- by {user}`sirosen`.

piptools/_compat/pip_compat.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import optparse
44
import pathlib
5+
import urllib.parse
56
from dataclasses import dataclass
67
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Set, cast
78

@@ -140,6 +141,10 @@ def _relativize_comes_from_location(original_comes_from: str, /) -> str:
140141
# split on the space
141142
prefix, space_sep, suffix = original_comes_from.partition(" ")
142143

144+
# if the value part is a remote URI for pip, return the original
145+
if _is_remote_pip_uri(suffix):
146+
return original_comes_from
147+
143148
file_path = pathlib.Path(suffix)
144149

145150
# if the path was not absolute, normalize to posix-style and finish processing
@@ -169,11 +174,26 @@ def _normalize_comes_from_location(original_comes_from: str, /) -> str:
169174
# split on the space
170175
prefix, space_sep, suffix = original_comes_from.partition(" ")
171176

177+
# if the value part is a remote URI for pip, return the original
178+
if _is_remote_pip_uri(suffix):
179+
return original_comes_from
180+
172181
# convert to a posix-style path
173182
suffix = pathlib.Path(suffix).as_posix()
174183
return f"{prefix}{space_sep}{suffix}"
175184

176185

186+
def _is_remote_pip_uri(value: str) -> bool:
187+
"""
188+
Test a string to see if it is a URI treated as a remote file in ``pip``.
189+
Specifically this means that it's a 'file', 'http', or 'https' URI.
190+
191+
The test is performed by trying a URL parse and reading the scheme.
192+
"""
193+
scheme = urllib.parse.urlsplit(value).scheme
194+
return scheme in {"file", "http", "https"}
195+
196+
177197
def create_wheel_cache(cache_dir: str, format_control: str | None = None) -> WheelCache:
178198
kwargs: dict[str, str | None] = {"cache_dir": cache_dir}
179199
if PIP_VERSION[:2] <= (23, 0):

tests/test_cli_compile.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from unittest.mock import MagicMock
1515

1616
import pytest
17+
from pip._internal.network.session import PipSession
1718
from pip._internal.req.constructors import install_req_from_line
1819
from pip._internal.utils.hashes import FAVORITE_HASH
1920
from pip._internal.utils.urls import path_to_url
@@ -4044,3 +4045,79 @@ def test_second_order_requirements_relative_path_in_separate_dir(
40444045
# via -r {output_path}
40454046
"""
40464047
)
4048+
4049+
4050+
@pytest.mark.parametrize(
4051+
"input_path_absolute", (True, False), ids=("absolute-input", "relative-input")
4052+
)
4053+
def test_url_constraints_are_not_treated_as_file_paths(
4054+
pip_conf,
4055+
make_package,
4056+
runner,
4057+
tmp_path,
4058+
monkeypatch,
4059+
input_path_absolute,
4060+
):
4061+
"""
4062+
Test normalization of ``-c`` constraints when the constraints are HTTPS URLs.
4063+
The constraints should be preserved verbatim.
4064+
4065+
This is a regression test for
4066+
https://github.com/jazzband/pip-tools/issues/2223
4067+
"""
4068+
constraints_url = "https://example.com/files/common_constraints.txt"
4069+
4070+
reqs_in = tmp_path / "requirements.in"
4071+
reqs_in.write_text(
4072+
f"""
4073+
small-fake-a
4074+
-c {constraints_url}
4075+
"""
4076+
)
4077+
4078+
input_dir_path = tmp_path if input_path_absolute else pathlib.Path(".")
4079+
input_path = (input_dir_path / "requirements.in").as_posix()
4080+
4081+
# TODO: find a better way of mocking the callout to get the constraints
4082+
# file (use `responses`?)
4083+
#
4084+
# we need a mock response for `GET https://...` as fetched by pip
4085+
# although this is fragile, it can be adapted if pip changes
4086+
def fake_url_get(url):
4087+
response = mock.Mock()
4088+
response.reason = "Ok"
4089+
response.status_code = 200
4090+
response.url = url
4091+
response.text = "small-fake-a==0.2"
4092+
return response
4093+
4094+
mock_get = mock.Mock(side_effect=fake_url_get)
4095+
4096+
with monkeypatch.context() as revertable_ctx:
4097+
revertable_ctx.chdir(tmp_path)
4098+
revertable_ctx.setattr(PipSession, "get", mock_get)
4099+
out = runner.invoke(
4100+
cli,
4101+
[
4102+
"--output-file",
4103+
"-",
4104+
"--quiet",
4105+
"--no-header",
4106+
"--no-emit-options",
4107+
"-r",
4108+
input_path,
4109+
],
4110+
)
4111+
4112+
# sanity check, pip should have tried to fetch the constraints
4113+
mock_get.assert_called_once_with(constraints_url)
4114+
4115+
assert out.exit_code == 0
4116+
assert out.stdout == dedent(
4117+
f"""\
4118+
small-fake-a==0.2
4119+
# via
4120+
# -c {constraints_url}
4121+
# -r {input_path}
4122+
"""
4123+
)

0 commit comments

Comments
 (0)