summaryrefslogtreecommitdiff
path: root/src/pip/_internal/resolution/resolvelib/base.py
blob: 0295b0ed8d22655aee7d931e4a27194b2b4bd723 (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
141
142
143
144
145
146
147
148
149
150
from typing import FrozenSet, Iterable, Optional, Tuple, Union

from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import LegacyVersion, Version

from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.hashes import Hashes

CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]]
CandidateVersion = Union[LegacyVersion, Version]


def format_name(project, extras):
    # type: (str, FrozenSet[str]) -> str
    if not extras:
        return project
    canonical_extras = sorted(canonicalize_name(e) for e in extras)
    return "{}[{}]".format(project, ",".join(canonical_extras))


class Constraint:
    def __init__(self, specifier, hashes):
        # type: (SpecifierSet, Hashes) -> None
        self.specifier = specifier
        self.hashes = hashes

    @classmethod
    def empty(cls):
        # type: () -> Constraint
        return Constraint(SpecifierSet(), Hashes())

    @classmethod
    def from_ireq(cls, ireq):
        # type: (InstallRequirement) -> Constraint
        return Constraint(ireq.specifier, ireq.hashes(trust_internet=False))

    def __nonzero__(self):
        # type: () -> bool
        return bool(self.specifier) or bool(self.hashes)

    def __bool__(self):
        # type: () -> bool
        return self.__nonzero__()

    def __and__(self, other):
        # type: (InstallRequirement) -> Constraint
        if not isinstance(other, InstallRequirement):
            return NotImplemented
        specifier = self.specifier & other.specifier
        hashes = self.hashes & other.hashes(trust_internet=False)
        return Constraint(specifier, hashes)

    def is_satisfied_by(self, candidate):
        # type: (Candidate) -> bool
        # We can safely always allow prereleases here since PackageFinder
        # already implements the prerelease logic, and would have filtered out
        # prerelease candidates if the user does not expect them.
        return self.specifier.contains(candidate.version, prereleases=True)


class Requirement:
    @property
    def project_name(self):
        # type: () -> NormalizedName
        """The "project name" of a requirement.

        This is different from ``name`` if this requirement contains extras,
        in which case ``name`` would contain the ``[...]`` part, while this
        refers to the name of the project.
        """
        raise NotImplementedError("Subclass should override")

    @property
    def name(self):
        # type: () -> str
        """The name identifying this requirement in the resolver.

        This is different from ``project_name`` if this requirement contains
        extras, where ``project_name`` would not contain the ``[...]`` part.
        """
        raise NotImplementedError("Subclass should override")

    def is_satisfied_by(self, candidate):
        # type: (Candidate) -> bool
        return False

    def get_candidate_lookup(self):
        # type: () -> CandidateLookup
        raise NotImplementedError("Subclass should override")

    def format_for_error(self):
        # type: () -> str
        raise NotImplementedError("Subclass should override")


class Candidate:
    @property
    def project_name(self):
        # type: () -> NormalizedName
        """The "project name" of the candidate.

        This is different from ``name`` if this candidate contains extras,
        in which case ``name`` would contain the ``[...]`` part, while this
        refers to the name of the project.
        """
        raise NotImplementedError("Override in subclass")

    @property
    def name(self):
        # type: () -> str
        """The name identifying this candidate in the resolver.

        This is different from ``project_name`` if this candidate contains
        extras, where ``project_name`` would not contain the ``[...]`` part.
        """
        raise NotImplementedError("Override in subclass")

    @property
    def version(self):
        # type: () -> CandidateVersion
        raise NotImplementedError("Override in subclass")

    @property
    def is_installed(self):
        # type: () -> bool
        raise NotImplementedError("Override in subclass")

    @property
    def is_editable(self):
        # type: () -> bool
        raise NotImplementedError("Override in subclass")

    @property
    def source_link(self):
        # type: () -> Optional[Link]
        raise NotImplementedError("Override in subclass")

    def iter_dependencies(self, with_requires):
        # type: (bool) -> Iterable[Optional[Requirement]]
        raise NotImplementedError("Override in subclass")

    def get_install_requirement(self):
        # type: () -> Optional[InstallRequirement]
        raise NotImplementedError("Override in subclass")

    def format_for_error(self):
        # type: () -> str
        raise NotImplementedError("Subclass should override")