Skip to content

Commit

Permalink
fix(ux): better error report when no links found
Browse files Browse the repository at this point in the history
  • Loading branch information
abn committed Jan 31, 2025
1 parent 8b13bb8 commit 909c59d
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 28 deletions.
84 changes: 83 additions & 1 deletion src/poetry/installation/chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,16 @@ def choose_for(self, package: Package) -> Link:
Return the url of the selected archive for a given package.
"""
links = []

# these are used only for providing insightful errors to the user
unsupported_wheels = set()
links_seen = 0
wheels_skipped = 0
sdists_skipped = 0

for link in self._get_links(package):
links_seen += 1

if link.is_wheel:
if not self._no_binary_policy.allows(package.name):
logger.debug(
Expand All @@ -59,6 +68,7 @@ def choose_for(self, package: Package) -> Link:
link.filename,
package.name,
)
wheels_skipped += 1
continue

if not Wheel(link.filename).is_supported_by_environment(self._env):
Expand All @@ -67,6 +77,7 @@ def choose_for(self, package: Package) -> Link:
" environment",
link.filename,
)
unsupported_wheels.add(link.filename)
continue

if link.ext in {".egg", ".exe", ".msi", ".rpm", ".srpm"}:
Expand All @@ -80,18 +91,89 @@ def choose_for(self, package: Package) -> Link:
link.filename,
package.name,
)
sdists_skipped += 1
continue

links.append(link)

if not links:
raise RuntimeError(f"Unable to find installation candidates for {package}")
raise self._no_links_found_error(
package, links_seen, wheels_skipped, sdists_skipped, unsupported_wheels
)

# Get the best link
chosen = max(links, key=lambda link: self._sort_key(package, link))

return chosen

def _no_links_found_error(
self,
package: Package,
links_seen: int,
wheels_skipped: int,
sdists_skipped: int,
unsupported_wheels: set[str],
) -> PoetryRuntimeError:
messages = []
info = (
f"This is likely not a Poetry issue.\n\n"
f" - {links_seen} candidates were identified for the package\n"
)

if wheels_skipped > 0:
info += f" - {wheels_skipped} wheels were skipped due to your <c1>installer.no-binary</> policy\n"

if sdists_skipped > 0:
info += f" - {sdists_skipped} source distributions were skipped due to your <c1>installer.only-binary</> policy\n"

if unsupported_wheels:
info += (
f" - {len(unsupported_wheels)} wheels were skipped as your project's environment does not support "
f"the identified abi tags\n"
)

messages.append(ConsoleMessage(info.strip()))

if unsupported_wheels:
messages += [
ConsoleMessage(
"The following wheels were skipped as the current project environment does not support them "
"due to abi compatibility issues.",
debug=True,
),
ConsoleMessage("\n".join(unsupported_wheels), debug=True)
.indent(" - ")
.wrap("warning"),
ConsoleMessage(
"If you would like to see the supported tags in your project environment, you can execute "
"the following command:\n\n"
" <c1>poetry debug tags</>",
debug=True,
),
]

source_hint = ""
if package.source_type and package.source_reference:
source_hint += f" ({package.source_reference})"

messages.append(
ConsoleMessage(
f"Make sure the lockfile is up-to-date. You can try one of the following;\n\n"
f" 1. <b>Regenerate lockfile: </><fg=yellow>poetry lock --no-cache --regenerate</>\n"
f" 2. <b>Update package : </><fg=yellow>poetry update --no-cache {package.name}</>\n\n"
f"If neither works, please first check to verify that the {package.name} has published wheels "
f"available from your configured source{source_hint} that are compatible with your environment"
f"- ie. operating system, architecture (x86_64, arm64 etc.), python interpreter."
)
.make_section("Solutions")
.wrap("info")
)

return PoetryRuntimeError(
reason=f"Unable to find installation candidates for {package}",
messages=messages,
)

def _get_links(self, package: Package) -> list[Link]:
if package.source_type:
assert package.source_reference is not None
Expand Down
34 changes: 34 additions & 0 deletions tests/installation/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

import pytest

from packaging.tags import Tag

from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pypi_repository import PyPiRepository
from poetry.repositories.repository_pool import RepositoryPool
from poetry.utils.env import MockEnv


@pytest.fixture()
def env() -> MockEnv:
return MockEnv(
supported_tags=[
Tag("cp37", "cp37", "macosx_10_15_x86_64"),
Tag("py3", "none", "any"),
]
)


@pytest.fixture()
def pool(legacy_repository: LegacyRepository) -> RepositoryPool:
pool = RepositoryPool()

pool.add_repository(PyPiRepository(disable_cache=True))
pool.add_repository(
LegacyRepository("foo", "https://legacy.foo.bar/simple/", disable_cache=True)
)
pool.add_repository(
LegacyRepository("foo2", "https://legacy.foo2.bar/simple/", disable_cache=True)
)
return pool
115 changes: 88 additions & 27 deletions tests/installation/test_chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
from poetry.console.exceptions import PoetryRuntimeError
from poetry.installation.chooser import Chooser
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pypi_repository import PyPiRepository
from poetry.repositories.repository_pool import RepositoryPool
from poetry.utils.env import MockEnv


if TYPE_CHECKING:
from poetry.repositories.repository_pool import RepositoryPool
from tests.conftest import Config
from tests.types import DistributionHashGetter
from tests.types import SpecializedLegacyRepositoryMocker
Expand All @@ -28,30 +27,6 @@
LEGACY_FIXTURES = Path(__file__).parent.parent / "repositories" / "fixtures" / "legacy"


@pytest.fixture()
def env() -> MockEnv:
return MockEnv(
supported_tags=[
Tag("cp37", "cp37", "macosx_10_15_x86_64"),
Tag("py3", "none", "any"),
]
)


@pytest.fixture()
def pool(legacy_repository: LegacyRepository) -> RepositoryPool:
pool = RepositoryPool()

pool.add_repository(PyPiRepository(disable_cache=True))
pool.add_repository(
LegacyRepository("foo", "https://legacy.foo.bar/simple/", disable_cache=True)
)
pool.add_repository(
LegacyRepository("foo2", "https://legacy.foo2.bar/simple/", disable_cache=True)
)
return pool


def check_chosen_link_filename(
env: MockEnv,
source_type: str,
Expand All @@ -75,7 +50,7 @@ def check_chosen_link_filename(

try:
link = chooser.choose_for(package)
except RuntimeError as e:
except PoetryRuntimeError as e:
if filename is None:
assert (
str(e)
Expand Down Expand Up @@ -398,3 +373,89 @@ def test_chooser_md5_remote_fallback_to_sha256_inline_calculation(
]
res = chooser.choose_for(package)
assert res.filename == "demo-0.1.0.tar.gz"


def test_chooser_no_links_found_error(env: MockEnv, pool: RepositoryPool) -> None:
chooser = Chooser(pool, env)
package = Package(
"demo",
"0.1.0",
source_type="legacy",
source_reference="foo",
source_url="https://legacy.foo.bar/simple/",
)

error = chooser._no_links_found_error(
package=package,
links_seen=4,
wheels_skipped=3,
sdists_skipped=1,
unsupported_wheels={"demo-0.1.0-py3-none-any.whl"},
)
assert (
error.get_text(debug=True, strip=True)
== """\
Unable to find installation candidates for demo (0.1.0)
This is likely not a Poetry issue.
- 4 candidates were identified for the package
- 3 wheels were skipped due to your installer.no-binary policy
- 1 source distributions were skipped due to your installer.only-binary policy
- 1 wheels were skipped as your project's environment does not support the identified abi tags
The following wheels were skipped as the current project environment does not support them due to abi compatibility \
issues.
- demo-0.1.0-py3-none-any.whl
If you would like to see the supported tags in your project environment, you can execute the following command:
poetry debug tags
Solutions:
Make sure the lockfile is up-to-date. You can try one of the following;
1. Regenerate lockfile: poetry lock --no-cache --regenerate
2. Update package : poetry update --no-cache demo
If neither works, please first check to verify that the demo has published wheels available from your configured \
source (foo) that are compatible with your environment- ie. operating system, architecture (x86_64, arm64 etc.), \
python interpreter.\
"""
)

assert (
error.get_text(debug=True, strip=True)
== """\
Unable to find installation candidates for demo (0.1.0)
This is likely not a Poetry issue.
- 4 candidates were identified for the package
- 3 wheels were skipped due to your installer.no-binary policy
- 1 source distributions were skipped due to your installer.only-binary policy
- 1 wheels were skipped as your project's environment does not support the identified abi tags
The following wheels were skipped as the current project environment does not support them due to abi compatibility \
issues.
- demo-0.1.0-py3-none-any.whl
If you would like to see the supported tags in your project environment, you can execute the following command:
poetry debug tags
Solutions:
Make sure the lockfile is up-to-date. You can try one of the following;
1. Regenerate lockfile: poetry lock --no-cache --regenerate
2. Update package : poetry update --no-cache demo
If neither works, please first check to verify that the demo has published wheels available from your configured \
source (foo) that are compatible with your environment- ie. operating system, architecture (x86_64, arm64 etc.), \
python interpreter.\
"""
)

0 comments on commit 909c59d

Please sign in to comment.