Commit 820c503f by Arun Babu Neelicattu Committed by Bjorn Neergaard

repo: handle invalid distributions gracefully

This change ensures that poetry does not bail out when encountering a
bad distributions in `sys_path`. This behaviour is similar to how `pip`
handles this today. In addition to ignoring these distributions, we
also issue a warning log so users can choose to act on this.

Further, this change also handles a scenario where an empty path is
present in the `sys_path`.
parent 66508bc6
...@@ -2,6 +2,7 @@ from __future__ import annotations ...@@ -2,6 +2,7 @@ from __future__ import annotations
import itertools import itertools
import json import json
import logging
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
...@@ -28,6 +29,9 @@ except NameError: ...@@ -28,6 +29,9 @@ except NameError:
FileNotFoundError = OSError FileNotFoundError = OSError
logger = logging.getLogger(__name__)
class InstalledRepository(Repository): class InstalledRepository(Repository):
@classmethod @classmethod
def get_package_paths(cls, env: Env, name: str) -> set[Path]: def get_package_paths(cls, env: Env, name: str) -> set[Path]:
...@@ -233,20 +237,41 @@ class InstalledRepository(Repository): ...@@ -233,20 +237,41 @@ class InstalledRepository(Repository):
repo = cls() repo = cls()
seen = set() seen = set()
skipped = set()
for entry in reversed(env.sys_path): for entry in reversed(env.sys_path):
if not entry.strip():
logger.debug(
"Project environment contains an empty path in <c1>sys_path</>,"
" ignoring."
)
continue
for distribution in sorted( for distribution in sorted(
metadata.distributions( # type: ignore[no-untyped-call] metadata.distributions( # type: ignore[no-untyped-call]
path=[entry], path=[entry],
), ),
key=lambda d: str(d._path), # type: ignore[attr-defined] key=lambda d: str(d._path), # type: ignore[attr-defined]
): ):
name = canonicalize_name(distribution.metadata["name"]) path = Path(str(distribution._path)) # type: ignore[attr-defined]
if name in seen: if path in skipped:
continue continue
path = Path(str(distribution._path)) # type: ignore[attr-defined] try:
name = canonicalize_name(distribution.metadata["name"])
except TypeError:
logger.warning(
"Project environment contains an invalid distribution"
" (<c1>%s</>). Consider removing it manually or recreate the"
" environment.",
path,
)
skipped.add(path)
continue
if name in seen:
continue
try: try:
path.relative_to(_VENDORS) path.relative_to(_VENDORS)
......
...@@ -13,6 +13,7 @@ from tests.compat import zipp ...@@ -13,6 +13,7 @@ from tests.compat import zipp
if TYPE_CHECKING: if TYPE_CHECKING:
from _pytest.logging import LogCaptureFixture
from poetry.core.packages.package import Package from poetry.core.packages.package import Package
from pytest_mock.plugin import MockerFixture from pytest_mock.plugin import MockerFixture
...@@ -103,6 +104,27 @@ def test_load_successful(repository: InstalledRepository): ...@@ -103,6 +104,27 @@ def test_load_successful(repository: InstalledRepository):
assert len(repository.packages) == len(INSTALLED_RESULTS) - 1 assert len(repository.packages) == len(INSTALLED_RESULTS) - 1
def test_load_successful_with_invalid_distribution(
caplog: LogCaptureFixture, mocker: MockerFixture, env: MockEnv, tmp_dir: str
) -> None:
invalid_dist_info = Path(tmp_dir) / "site-packages" / "invalid-0.1.0.dist-info"
invalid_dist_info.mkdir(parents=True)
mocker.patch(
"poetry.utils._compat.metadata.Distribution.discover",
return_value=INSTALLED_RESULTS + [metadata.PathDistribution(invalid_dist_info)],
)
repository_with_invalid_distribution = InstalledRepository.load(env)
assert (
len(repository_with_invalid_distribution.packages) == len(INSTALLED_RESULTS) - 1
)
assert len(caplog.messages) == 1
message = caplog.messages[0]
assert message.startswith("Project environment contains an invalid distribution")
assert str(invalid_dist_info) in message
def test_load_ensure_isolation(repository: InstalledRepository): def test_load_ensure_isolation(repository: InstalledRepository):
package = get_package_from_repository("attrs", repository) package = get_package_from_repository("attrs", repository)
assert package is None assert package is None
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment