requirements.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. from typing import Any, Optional
  2. from pip._vendor.packaging.specifiers import SpecifierSet
  3. from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
  4. from pip._internal.req.constructors import install_req_drop_extras
  5. from pip._internal.req.req_install import InstallRequirement
  6. from .base import Candidate, CandidateLookup, Requirement, format_name
  7. class ExplicitRequirement(Requirement):
  8. def __init__(self, candidate: Candidate) -> None:
  9. self.candidate = candidate
  10. def __str__(self) -> str:
  11. return str(self.candidate)
  12. def __repr__(self) -> str:
  13. return f"{self.__class__.__name__}({self.candidate!r})"
  14. def __hash__(self) -> int:
  15. return hash(self.candidate)
  16. def __eq__(self, other: Any) -> bool:
  17. if not isinstance(other, ExplicitRequirement):
  18. return False
  19. return self.candidate == other.candidate
  20. @property
  21. def project_name(self) -> NormalizedName:
  22. # No need to canonicalize - the candidate did this
  23. return self.candidate.project_name
  24. @property
  25. def name(self) -> str:
  26. # No need to canonicalize - the candidate did this
  27. return self.candidate.name
  28. def format_for_error(self) -> str:
  29. return self.candidate.format_for_error()
  30. def get_candidate_lookup(self) -> CandidateLookup:
  31. return self.candidate, None
  32. def is_satisfied_by(self, candidate: Candidate) -> bool:
  33. return candidate == self.candidate
  34. class SpecifierRequirement(Requirement):
  35. def __init__(self, ireq: InstallRequirement) -> None:
  36. assert ireq.link is None, "This is a link, not a specifier"
  37. self._ireq = ireq
  38. self._equal_cache: Optional[str] = None
  39. self._hash: Optional[int] = None
  40. self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
  41. @property
  42. def _equal(self) -> str:
  43. if self._equal_cache is not None:
  44. return self._equal_cache
  45. self._equal_cache = str(self._ireq)
  46. return self._equal_cache
  47. def __str__(self) -> str:
  48. return str(self._ireq.req)
  49. def __repr__(self) -> str:
  50. return f"{self.__class__.__name__}({str(self._ireq.req)!r})"
  51. def __eq__(self, other: object) -> bool:
  52. if not isinstance(other, SpecifierRequirement):
  53. return NotImplemented
  54. return self._equal == other._equal
  55. def __hash__(self) -> int:
  56. if self._hash is not None:
  57. return self._hash
  58. self._hash = hash(self._equal)
  59. return self._hash
  60. @property
  61. def project_name(self) -> NormalizedName:
  62. assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
  63. return canonicalize_name(self._ireq.req.name)
  64. @property
  65. def name(self) -> str:
  66. return format_name(self.project_name, self._extras)
  67. def format_for_error(self) -> str:
  68. # Convert comma-separated specifiers into "A, B, ..., F and G"
  69. # This makes the specifier a bit more "human readable", without
  70. # risking a change in meaning. (Hopefully! Not all edge cases have
  71. # been checked)
  72. parts = [s.strip() for s in str(self).split(",")]
  73. if len(parts) == 0:
  74. return ""
  75. elif len(parts) == 1:
  76. return parts[0]
  77. return ", ".join(parts[:-1]) + " and " + parts[-1]
  78. def get_candidate_lookup(self) -> CandidateLookup:
  79. return None, self._ireq
  80. def is_satisfied_by(self, candidate: Candidate) -> bool:
  81. assert candidate.name == self.name, (
  82. f"Internal issue: Candidate is not for this requirement "
  83. f"{candidate.name} vs {self.name}"
  84. )
  85. # We can safely always allow prereleases here since PackageFinder
  86. # already implements the prerelease logic, and would have filtered out
  87. # prerelease candidates if the user does not expect them.
  88. assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
  89. spec = self._ireq.req.specifier
  90. return spec.contains(candidate.version, prereleases=True)
  91. class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
  92. """
  93. Requirement backed by an install requirement on a base package.
  94. Trims extras from its install requirement if there are any.
  95. """
  96. def __init__(self, ireq: InstallRequirement) -> None:
  97. assert ireq.link is None, "This is a link, not a specifier"
  98. self._ireq = install_req_drop_extras(ireq)
  99. self._equal_cache: Optional[str] = None
  100. self._hash: Optional[int] = None
  101. self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
  102. @property
  103. def _equal(self) -> str:
  104. if self._equal_cache is not None:
  105. return self._equal_cache
  106. self._equal_cache = str(self._ireq)
  107. return self._equal_cache
  108. def __eq__(self, other: object) -> bool:
  109. if not isinstance(other, SpecifierWithoutExtrasRequirement):
  110. return NotImplemented
  111. return self._equal == other._equal
  112. def __hash__(self) -> int:
  113. if self._hash is not None:
  114. return self._hash
  115. self._hash = hash(self._equal)
  116. return self._hash
  117. class RequiresPythonRequirement(Requirement):
  118. """A requirement representing Requires-Python metadata."""
  119. def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
  120. self.specifier = specifier
  121. self._specifier_string = str(specifier) # for faster __eq__
  122. self._hash: Optional[int] = None
  123. self._candidate = match
  124. def __str__(self) -> str:
  125. return f"Python {self.specifier}"
  126. def __repr__(self) -> str:
  127. return f"{self.__class__.__name__}({str(self.specifier)!r})"
  128. def __hash__(self) -> int:
  129. if self._hash is not None:
  130. return self._hash
  131. self._hash = hash((self._specifier_string, self._candidate))
  132. return self._hash
  133. def __eq__(self, other: Any) -> bool:
  134. if not isinstance(other, RequiresPythonRequirement):
  135. return False
  136. return (
  137. self._specifier_string == other._specifier_string
  138. and self._candidate == other._candidate
  139. )
  140. @property
  141. def project_name(self) -> NormalizedName:
  142. return self._candidate.project_name
  143. @property
  144. def name(self) -> str:
  145. return self._candidate.name
  146. def format_for_error(self) -> str:
  147. return str(self)
  148. def get_candidate_lookup(self) -> CandidateLookup:
  149. if self.specifier.contains(self._candidate.version, prereleases=True):
  150. return self._candidate, None
  151. return None, None
  152. def is_satisfied_by(self, candidate: Candidate) -> bool:
  153. assert candidate.name == self._candidate.name, "Not Python candidate"
  154. # We can safely always allow prereleases here since PackageFinder
  155. # already implements the prerelease logic, and would have filtered out
  156. # prerelease candidates if the user does not expect them.
  157. return self.specifier.contains(candidate.version, prereleases=True)
  158. class UnsatisfiableRequirement(Requirement):
  159. """A requirement that cannot be satisfied."""
  160. def __init__(self, name: NormalizedName) -> None:
  161. self._name = name
  162. def __str__(self) -> str:
  163. return f"{self._name} (unavailable)"
  164. def __repr__(self) -> str:
  165. return f"{self.__class__.__name__}({str(self._name)!r})"
  166. def __eq__(self, other: object) -> bool:
  167. if not isinstance(other, UnsatisfiableRequirement):
  168. return NotImplemented
  169. return self._name == other._name
  170. def __hash__(self) -> int:
  171. return hash(self._name)
  172. @property
  173. def project_name(self) -> NormalizedName:
  174. return self._name
  175. @property
  176. def name(self) -> str:
  177. return self._name
  178. def format_for_error(self) -> str:
  179. return str(self)
  180. def get_candidate_lookup(self) -> CandidateLookup:
  181. return None, None
  182. def is_satisfied_by(self, candidate: Candidate) -> bool:
  183. return False