Skip to content

Commit

Permalink
repo: handle invalid distributions gracefully
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
abn committed May 19, 2022
1 parent 592ba26 commit 5f68912
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 3 deletions.
31 changes: 28 additions & 3 deletions src/poetry/repositories/installed_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import itertools
import json
import logging

from pathlib import Path
from typing import TYPE_CHECKING
Expand All @@ -28,6 +29,9 @@
FileNotFoundError = OSError


logger = logging.getLogger(__name__)


class InstalledRepository(Repository):
@classmethod
def get_package_paths(cls, env: Env, name: str) -> set[Path]:
Expand Down Expand Up @@ -233,20 +237,41 @@ def load(cls, env: Env, with_dependencies: bool = False) -> InstalledRepository:

repo = cls()
seen = set()
skipped = set()

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(
metadata.distributions( # type: ignore[no-untyped-call]
path=[entry],
),
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

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:
path.relative_to(_VENDORS)
Expand Down
22 changes: 22 additions & 0 deletions tests/repositories/test_installed_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@


if TYPE_CHECKING:
from _pytest.logging import LogCaptureFixture
from poetry.core.packages.package import Package
from pytest_mock.plugin import MockerFixture

Expand Down Expand Up @@ -103,6 +104,27 @@ def test_load_successful(repository: InstalledRepository):
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):
package = get_package_from_repository("attrs", repository)
assert package is None
Expand Down

0 comments on commit 5f68912

Please # to comment.