1"""
2Special requirement and candidate classes to describe a requires-python constraint
3"""
4
5from typing import Iterable, Iterator, Mapping, cast
6
7from pdm import termui
8from pdm.models.candidates import Candidate
9from pdm.models.environment import Environment
10from pdm.models.requirements import NamedRequirement, Requirement
11from pdm.models.specifiers import PySpecSet
12
13
14class PythonCandidate(Candidate):
15    def format(self) -> str:
16        return (
17            f"{termui.green(self.name, bold=True)} "
18            f"{termui.yellow(str(self.req.specifier))}"
19        )
20
21
22class PythonRequirement(NamedRequirement):
23    @classmethod
24    def from_pyspec_set(cls, spec: PySpecSet) -> "PythonRequirement":
25        return cls(name="python", specifier=spec)
26
27    def as_candidate(self, environment: Environment) -> PythonCandidate:
28        return PythonCandidate(self, environment)
29
30
31def find_python_matches(
32    identifier: str,
33    requirements: Mapping[str, Iterator[Requirement]],
34    environment: Environment,
35) -> Iterable[Candidate]:
36    """All requires-python except for the first one(must come from the project)
37    must be superset of the first one.
38    """
39    python_reqs = cast(Iterator[PythonRequirement], iter(requirements[identifier]))
40    project_req = next(python_reqs)
41    python_specs = cast(Iterator[PySpecSet], (req.specifier for req in python_reqs))
42    if all(spec.is_superset(project_req.specifier or "") for spec in python_specs):
43        return [project_req.as_candidate(environment)]
44    else:
45        # There is a conflict, no match is found.
46        return []
47
48
49def is_python_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool:
50    return cast(PySpecSet, requirement.specifier).is_superset(candidate.req.specifier)
51