Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

req_set.py 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from __future__ import absolute_import
  2. import logging
  3. from collections import OrderedDict
  4. from pip._internal.exceptions import InstallationError
  5. from pip._internal.utils.logging import indent_log
  6. from pip._internal.wheel import Wheel
  7. logger = logging.getLogger(__name__)
  8. class RequirementSet(object):
  9. def __init__(self, require_hashes=False, check_supported_wheels=True):
  10. """Create a RequirementSet.
  11. """
  12. self.requirements = OrderedDict()
  13. self.require_hashes = require_hashes
  14. self.check_supported_wheels = check_supported_wheels
  15. # Mapping of alias: real_name
  16. self.requirement_aliases = {}
  17. self.unnamed_requirements = []
  18. self.successfully_downloaded = []
  19. self.reqs_to_cleanup = []
  20. def __str__(self):
  21. reqs = [req for req in self.requirements.values()
  22. if not req.comes_from]
  23. reqs.sort(key=lambda req: req.name.lower())
  24. return ' '.join([str(req.req) for req in reqs])
  25. def __repr__(self):
  26. reqs = [req for req in self.requirements.values()]
  27. reqs.sort(key=lambda req: req.name.lower())
  28. reqs_str = ', '.join([str(req.req) for req in reqs])
  29. return ('<%s object; %d requirement(s): %s>'
  30. % (self.__class__.__name__, len(reqs), reqs_str))
  31. def add_requirement(self, install_req, parent_req_name=None,
  32. extras_requested=None):
  33. """Add install_req as a requirement to install.
  34. :param parent_req_name: The name of the requirement that needed this
  35. added. The name is used because when multiple unnamed requirements
  36. resolve to the same name, we could otherwise end up with dependency
  37. links that point outside the Requirements set. parent_req must
  38. already be added. Note that None implies that this is a user
  39. supplied requirement, vs an inferred one.
  40. :param extras_requested: an iterable of extras used to evaluate the
  41. environment markers.
  42. :return: Additional requirements to scan. That is either [] if
  43. the requirement is not applicable, or [install_req] if the
  44. requirement is applicable and has just been added.
  45. """
  46. name = install_req.name
  47. # If the markers do not match, ignore this requirement.
  48. if not install_req.match_markers(extras_requested):
  49. logger.info(
  50. "Ignoring %s: markers '%s' don't match your environment",
  51. name, install_req.markers,
  52. )
  53. return [], None
  54. # If the wheel is not supported, raise an error.
  55. # Should check this after filtering out based on environment markers to
  56. # allow specifying different wheels based on the environment/OS, in a
  57. # single requirements file.
  58. if install_req.link and install_req.link.is_wheel:
  59. wheel = Wheel(install_req.link.filename)
  60. if self.check_supported_wheels and not wheel.supported():
  61. raise InstallationError(
  62. "%s is not a supported wheel on this platform." %
  63. wheel.filename
  64. )
  65. # This next bit is really a sanity check.
  66. assert install_req.is_direct == (parent_req_name is None), (
  67. "a direct req shouldn't have a parent and also, "
  68. "a non direct req should have a parent"
  69. )
  70. # Unnamed requirements are scanned again and the requirement won't be
  71. # added as a dependency until after scanning.
  72. if not name:
  73. # url or path requirement w/o an egg fragment
  74. self.unnamed_requirements.append(install_req)
  75. return [install_req], None
  76. try:
  77. existing_req = self.get_requirement(name)
  78. except KeyError:
  79. existing_req = None
  80. has_conflicting_requirement = (
  81. parent_req_name is None and
  82. existing_req and
  83. not existing_req.constraint and
  84. existing_req.extras == install_req.extras and
  85. existing_req.req.specifier != install_req.req.specifier
  86. )
  87. if has_conflicting_requirement:
  88. raise InstallationError(
  89. "Double requirement given: %s (already in %s, name=%r)"
  90. % (install_req, existing_req, name)
  91. )
  92. # When no existing requirement exists, add the requirement as a
  93. # dependency and it will be scanned again after.
  94. if not existing_req:
  95. self.requirements[name] = install_req
  96. # FIXME: what about other normalizations? E.g., _ vs. -?
  97. if name.lower() != name:
  98. self.requirement_aliases[name.lower()] = name
  99. # We'd want to rescan this requirements later
  100. return [install_req], install_req
  101. # Assume there's no need to scan, and that we've already
  102. # encountered this for scanning.
  103. if install_req.constraint or not existing_req.constraint:
  104. return [], existing_req
  105. does_not_satisfy_constraint = (
  106. install_req.link and
  107. not (
  108. existing_req.link and
  109. install_req.link.path == existing_req.link.path
  110. )
  111. )
  112. if does_not_satisfy_constraint:
  113. self.reqs_to_cleanup.append(install_req)
  114. raise InstallationError(
  115. "Could not satisfy constraints for '%s': "
  116. "installation from path or url cannot be "
  117. "constrained to a version" % name,
  118. )
  119. # If we're now installing a constraint, mark the existing
  120. # object for real installation.
  121. existing_req.constraint = False
  122. existing_req.extras = tuple(sorted(
  123. set(existing_req.extras) | set(install_req.extras)
  124. ))
  125. logger.debug(
  126. "Setting %s extras to: %s",
  127. existing_req, existing_req.extras,
  128. )
  129. # Return the existing requirement for addition to the parent and
  130. # scanning again.
  131. return [existing_req], existing_req
  132. def has_requirement(self, project_name):
  133. name = project_name.lower()
  134. if (name in self.requirements and
  135. not self.requirements[name].constraint or
  136. name in self.requirement_aliases and
  137. not self.requirements[self.requirement_aliases[name]].constraint):
  138. return True
  139. return False
  140. @property
  141. def has_requirements(self):
  142. return list(req for req in self.requirements.values() if not
  143. req.constraint) or self.unnamed_requirements
  144. def get_requirement(self, project_name):
  145. for name in project_name, project_name.lower():
  146. if name in self.requirements:
  147. return self.requirements[name]
  148. if name in self.requirement_aliases:
  149. return self.requirements[self.requirement_aliases[name]]
  150. raise KeyError("No project with the name %r" % project_name)
  151. def cleanup_files(self):
  152. """Clean up files, remove builds."""
  153. logger.debug('Cleaning up...')
  154. with indent_log():
  155. for req in self.reqs_to_cleanup:
  156. req.remove_temporary_source()