requirements.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. from pip._vendor.packaging.specifiers import SpecifierSet
  2. from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
  3. from pip._internal.req.req_install import InstallRequirement
  4. from .base import Candidate, CandidateLookup, Requirement, format_name
  5. class ExplicitRequirement(Requirement):
  6. def __init__(self, candidate: Candidate) -> None:
  7. self.candidate = candidate
  8. def __str__(self) -> str:
  9. return str(self.candidate)
  10. def __repr__(self) -> str:
  11. return "{class_name}({candidate!r})".format(
  12. class_name=self.__class__.__name__,
  13. candidate=self.candidate,
  14. )
  15. @property
  16. def project_name(self) -> NormalizedName:
  17. # No need to canonicalize - the candidate did this
  18. return self.candidate.project_name
  19. @property
  20. def name(self) -> str:
  21. # No need to canonicalize - the candidate did this
  22. return self.candidate.name
  23. def format_for_error(self) -> str:
  24. return self.candidate.format_for_error()
  25. def get_candidate_lookup(self) -> CandidateLookup:
  26. return self.candidate, None
  27. def is_satisfied_by(self, candidate: Candidate) -> bool:
  28. return candidate == self.candidate
  29. class SpecifierRequirement(Requirement):
  30. def __init__(self, ireq: InstallRequirement) -> None:
  31. assert ireq.link is None, "This is a link, not a specifier"
  32. self._ireq = ireq
  33. self._extras = frozenset(ireq.extras)
  34. def __str__(self) -> str:
  35. return str(self._ireq.req)
  36. def __repr__(self) -> str:
  37. return "{class_name}({requirement!r})".format(
  38. class_name=self.__class__.__name__,
  39. requirement=str(self._ireq.req),
  40. )
  41. @property
  42. def project_name(self) -> NormalizedName:
  43. assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
  44. return canonicalize_name(self._ireq.req.name)
  45. @property
  46. def name(self) -> str:
  47. return format_name(self.project_name, self._extras)
  48. def format_for_error(self) -> str:
  49. # Convert comma-separated specifiers into "A, B, ..., F and G"
  50. # This makes the specifier a bit more "human readable", without
  51. # risking a change in meaning. (Hopefully! Not all edge cases have
  52. # been checked)
  53. parts = [s.strip() for s in str(self).split(",")]
  54. if len(parts) == 0:
  55. return ""
  56. elif len(parts) == 1:
  57. return parts[0]
  58. return ", ".join(parts[:-1]) + " and " + parts[-1]
  59. def get_candidate_lookup(self) -> CandidateLookup:
  60. return None, self._ireq
  61. def is_satisfied_by(self, candidate: Candidate) -> bool:
  62. assert candidate.name == self.name, (
  63. f"Internal issue: Candidate is not for this requirement "
  64. f"{candidate.name} vs {self.name}"
  65. )
  66. # We can safely always allow prereleases here since PackageFinder
  67. # already implements the prerelease logic, and would have filtered out
  68. # prerelease candidates if the user does not expect them.
  69. assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
  70. spec = self._ireq.req.specifier
  71. return spec.contains(candidate.version, prereleases=True)
  72. class RequiresPythonRequirement(Requirement):
  73. """A requirement representing Requires-Python metadata."""
  74. def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
  75. self.specifier = specifier
  76. self._candidate = match
  77. def __str__(self) -> str:
  78. return f"Python {self.specifier}"
  79. def __repr__(self) -> str:
  80. return "{class_name}({specifier!r})".format(
  81. class_name=self.__class__.__name__,
  82. specifier=str(self.specifier),
  83. )
  84. @property
  85. def project_name(self) -> NormalizedName:
  86. return self._candidate.project_name
  87. @property
  88. def name(self) -> str:
  89. return self._candidate.name
  90. def format_for_error(self) -> str:
  91. return str(self)
  92. def get_candidate_lookup(self) -> CandidateLookup:
  93. if self.specifier.contains(self._candidate.version, prereleases=True):
  94. return self._candidate, None
  95. return None, None
  96. def is_satisfied_by(self, candidate: Candidate) -> bool:
  97. assert candidate.name == self._candidate.name, "Not Python candidate"
  98. # We can safely always allow prereleases here since PackageFinder
  99. # already implements the prerelease logic, and would have filtered out
  100. # prerelease candidates if the user does not expect them.
  101. return self.specifier.contains(candidate.version, prereleases=True)
  102. class UnsatisfiableRequirement(Requirement):
  103. """A requirement that cannot be satisfied."""
  104. def __init__(self, name: NormalizedName) -> None:
  105. self._name = name
  106. def __str__(self) -> str:
  107. return f"{self._name} (unavailable)"
  108. def __repr__(self) -> str:
  109. return "{class_name}({name!r})".format(
  110. class_name=self.__class__.__name__,
  111. name=str(self._name),
  112. )
  113. @property
  114. def project_name(self) -> NormalizedName:
  115. return self._name
  116. @property
  117. def name(self) -> str:
  118. return self._name
  119. def format_for_error(self) -> str:
  120. return str(self)
  121. def get_candidate_lookup(self) -> CandidateLookup:
  122. return None, None
  123. def is_satisfied_by(self, candidate: Candidate) -> bool:
  124. return False