Skip to content

Commit fe40a74

Browse files
committed
installer: print warning if yanked file is used for install
1 parent d82063c commit fe40a74

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

src/poetry/installation/executor.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(
7878
self._executed = {"install": 0, "update": 0, "uninstall": 0}
7979
self._skipped = {"install": 0, "update": 0, "uninstall": 0}
8080
self._sections: dict[int, SectionOutput] = {}
81+
self._yanked_warnings: list[str] = []
8182
self._lock = threading.Lock()
8283
self._shutdown = False
8384
self._hashes: dict[str, str] = {}
@@ -140,6 +141,7 @@ def execute(self, operations: list[Operation]) -> int:
140141
# We group operations by priority
141142
groups = itertools.groupby(operations, key=lambda o: -o.priority)
142143
self._sections = {}
144+
self._yanked_warnings = []
143145
for _, group in groups:
144146
tasks = []
145147
serial_operations = []
@@ -179,6 +181,9 @@ def execute(self, operations: list[Operation]) -> int:
179181

180182
break
181183

184+
for warning in self._yanked_warnings:
185+
self._io.write_error_line(f"<warning>Warning: {warning}</warning>")
186+
182187
return 1 if self._shutdown else 0
183188

184189
@staticmethod
@@ -609,6 +614,18 @@ def _install_git(self, operation: Install | Update) -> int:
609614
def _download(self, operation: Install | Update) -> Path:
610615
link = self._chooser.choose_for(operation.package)
611616

617+
if link.yanked:
618+
# Store yanked warnings in a list and print after installing, so they can't
619+
# be overlooked. Further, printing them in the concerning section would have
620+
# the risk of overwriting the warning, so it is only briefly visible.
621+
message = (
622+
f"The file chosen for install of {operation.package.pretty_name} "
623+
f"{operation.package.pretty_version} ({link.show_url}) is yanked."
624+
)
625+
if link.yanked_reason:
626+
message += f" Reason for being yanked: {link.yanked_reason}"
627+
self._yanked_warnings.append(message)
628+
612629
return self._download_link(operation, link)
613630

614631
def _download_link(self, operation: Install | Update, link: Link) -> Path:

tests/installation/test_executor.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from pytest_mock import MockerFixture
3333

3434
from poetry.config.config import Config
35+
from poetry.installation.operations.operation import Operation
3536
from poetry.utils.env import VirtualEnv
3637
from tests.types import FixtureDirGetter
3738

@@ -177,6 +178,61 @@ def test_execute_executes_a_batch_of_operations(
177178
assert pip_install.call_args.kwargs.get("editable", False)
178179

179180

181+
@pytest.mark.parametrize(
182+
"operations, has_warning",
183+
[
184+
(
185+
[Install(Package("black", "21.11b0")), Install(Package("pytest", "3.5.2"))],
186+
True,
187+
),
188+
(
189+
[
190+
Uninstall(Package("black", "21.11b0")),
191+
Uninstall(Package("pytest", "3.5.2")),
192+
],
193+
False,
194+
),
195+
(
196+
[
197+
Update(Package("black", "19.10b0"), Package("black", "21.11b0")),
198+
Update(Package("pytest", "3.5.1"), Package("pytest", "3.5.2")),
199+
],
200+
True,
201+
),
202+
],
203+
)
204+
def test_execute_prints_warning_for_yanked_package(
205+
config: Config,
206+
pool: Pool,
207+
io: BufferedIO,
208+
tmp_dir: str,
209+
mock_file_downloads: None,
210+
env: MockEnv,
211+
operations: list[Operation],
212+
has_warning: bool,
213+
):
214+
config.merge({"cache-dir": tmp_dir})
215+
216+
executor = Executor(env, pool, config, io)
217+
218+
return_code = executor.execute(operations)
219+
220+
expected = (
221+
"Warning: The file chosen for install of black 21.11b0 "
222+
"(black-21.11b0-py3-none-any.whl) is yanked. Reason for being yanked: "
223+
"Broken regex dependency. Use 21.11b1 instead."
224+
)
225+
error = io.fetch_error()
226+
assert return_code == 0
227+
assert "pytest" not in error
228+
if has_warning:
229+
assert expected in error
230+
assert error.count("is yanked") == 1
231+
else:
232+
assert expected not in error
233+
assert error.count("yanked") == 0
234+
235+
180236
def test_execute_shows_skipped_operations_if_verbose(
181237
config: Config, pool: Pool, io: BufferedIO, config_cache_dir: Path, env: MockEnv
182238
):

0 commit comments

Comments
 (0)