Skip to content

Commit

Permalink
Merge pull request #9669 from uranusjr/ignore-invalid-name-dist-info
Browse files Browse the repository at this point in the history
Ignore dist-info directories with invalid name
  • Loading branch information
uranusjr authored Feb 28, 2021
2 parents 550270c + e4349ae commit e7d6e54
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 20 deletions.
2 changes: 2 additions & 0 deletions news/7269.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Ignore ``.dist-info`` directories if the stem is not a valid Python distribution
name, so they don't show up in e.g. ``pip freeze``.
44 changes: 43 additions & 1 deletion src/pip/_internal/metadata/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import logging
import re
from typing import Container, Iterator, List, Optional

from pip._vendor.packaging.version import _BaseVersion

from pip._internal.utils.misc import stdlib_pkgs # TODO: Move definition here.

logger = logging.getLogger(__name__)


class BaseDistribution:
@property
def location(self):
# type: () -> Optional[str]
"""Where the distribution is loaded from.
A string value is not necessarily a filesystem path, since distributions
can be loaded from other sources, e.g. arbitrary zip archives. ``None``
means the distribution is created in-memory.
"""
raise NotImplementedError()

@property
def metadata_version(self):
# type: () -> Optional[str]
Expand Down Expand Up @@ -61,10 +76,37 @@ def get_distribution(self, name):
"""Given a requirement name, return the installed distributions."""
raise NotImplementedError()

def _iter_distributions(self):
# type: () -> Iterator[BaseDistribution]
"""Iterate through installed distributions.
This function should be implemented by subclass, but never called
directly. Use the public ``iter_distribution()`` instead, which
implements additional logic to make sure the distributions are valid.
"""
raise NotImplementedError()

def iter_distributions(self):
# type: () -> Iterator[BaseDistribution]
"""Iterate through installed distributions."""
raise NotImplementedError()
for dist in self._iter_distributions():
# Make sure the distribution actually comes from a valid Python
# packaging distribution. Pip's AdjacentTempDirectory leaves folders
# e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The
# valid project name pattern is taken from PEP 508.
project_name_valid = re.match(
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
dist.canonical_name,
flags=re.IGNORECASE,
)
if not project_name_valid:
logger.warning(
"Ignoring invalid distribution %s (%s)",
dist.canonical_name,
dist.location,
)
continue
yield dist

def iter_installed_distributions(
self,
Expand Down
7 changes: 6 additions & 1 deletion src/pip/_internal/metadata/pkg_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def from_wheel(cls, path, name):
dist = pkg_resources_distribution_for_wheel(zf, name, path)
return cls(dist)

@property
def location(self):
# type: () -> Optional[str]
return self._dist.location

@property
def metadata_version(self):
# type: () -> Optional[str]
Expand Down Expand Up @@ -115,7 +120,7 @@ def get_distribution(self, name):
return None
return self._search_distribution(name)

def iter_distributions(self):
def _iter_distributions(self):
# type: () -> Iterator[BaseDistribution]
for dist in self._ws:
yield Distribution(dist)
35 changes: 17 additions & 18 deletions tests/functional/test_freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from doctest import ELLIPSIS, OutputChecker

import pytest
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.pkg_resources import safe_name

from tests.lib import (
_create_test_package,
Expand Down Expand Up @@ -128,26 +130,23 @@ def fake_install(pkgname, dest):
)
for pkgname in valid_pkgnames + invalid_pkgnames:
fake_install(pkgname, script.site_packages_path)

result = script.pip('freeze', expect_stderr=True)
for pkgname in valid_pkgnames:
_check_output(
result.stdout,
'...{}==1.0...'.format(pkgname.replace('_', '-'))
)
for pkgname in invalid_pkgnames:
# Check that the full distribution repr is present.
dist_repr = '{} 1.0 ('.format(pkgname.replace('_', '-'))
expected = (
'...Could not generate requirement for '
'distribution {}...'.format(dist_repr)
)
_check_output(result.stderr, expected)

# Also check that the parse error details occur at least once.
# We only need to find one occurrence to know that exception details
# are logged.
expected = '...site-packages): Parse error at "...'
_check_output(result.stderr, expected)
# Check all valid names are in the output.
output_lines = {line.strip() for line in result.stdout.splitlines()}
for name in valid_pkgnames:
assert f"{safe_name(name)}==1.0" in output_lines

# Check all invalid names are excluded from the output.
canonical_invalid_names = {canonicalize_name(n) for n in invalid_pkgnames}
for line in output_lines:
output_name, _, _ = line.partition("=")
assert canonicalize_name(output_name) not in canonical_invalid_names

# The invalid names should be logged.
for name in canonical_invalid_names:
assert f"Ignoring invalid distribution {name} (" in result.stderr


@pytest.mark.git
Expand Down

0 comments on commit e7d6e54

Please sign in to comment.