base.py 4.64 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
import logging
import re
from typing import Container, Iterator, List, Optional, Union

from pip._vendor.packaging.version import LegacyVersion, Version

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

DistributionVersion = Union[LegacyVersion, Version]

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]
        """Value of "Metadata-Version:" in the distribution, if available."""
        raise NotImplementedError()

    @property
    def canonical_name(self):
        # type: () -> str
        raise NotImplementedError()

    @property
    def version(self):
        # type: () -> DistributionVersion
        raise NotImplementedError()

    @property
    def installer(self):
        # type: () -> str
        raise NotImplementedError()

    @property
    def editable(self):
        # type: () -> bool
        raise NotImplementedError()

    @property
    def local(self):
        # type: () -> bool
        raise NotImplementedError()

    @property
    def in_usersite(self):
        # type: () -> bool
        raise NotImplementedError()


class BaseEnvironment:
    """An environment containing distributions to introspect."""

    @classmethod
    def default(cls):
        # type: () -> BaseEnvironment
        raise NotImplementedError()

    @classmethod
    def from_paths(cls, paths):
        # type: (Optional[List[str]]) -> BaseEnvironment
        raise NotImplementedError()

    def get_distribution(self, name):
        # type: (str) -> Optional[BaseDistribution]
        """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."""
        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,
        local_only=True,  # type: bool
        skip=stdlib_pkgs,  # type: Container[str]
        include_editables=True,  # type: bool
        editables_only=False,  # type: bool
        user_only=False,  # type: bool
    ):
        # type: (...) -> Iterator[BaseDistribution]
        """Return a list of installed distributions.

        :param local_only: If True (default), only return installations
        local to the current virtualenv, if in a virtualenv.
        :param skip: An iterable of canonicalized project names to ignore;
            defaults to ``stdlib_pkgs``.
        :param include_editables: If False, don't report editables.
        :param editables_only: If True, only report editables.
        :param user_only: If True, only report installations in the user
        site directory.
        """
        it = self.iter_distributions()
        if local_only:
            it = (d for d in it if d.local)
        if not include_editables:
            it = (d for d in it if not d.editable)
        if editables_only:
            it = (d for d in it if d.editable)
        if user_only:
            it = (d for d in it if d.in_usersite)
        return (d for d in it if d.canonical_name not in skip)