summaryrefslogtreecommitdiff
path: root/src/pip/_internal/metadata/base.py
blob: 100168b6ed2a285e2a8d7836a0e3f019f8f1c021 (plain)
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
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]
        """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: () -> _BaseVersion
        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)