req_command.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. """Contains the Command base classes that depend on PipSession.
  2. The classes in this module are in a separate module so the commands not
  3. needing download / PackageFinder capability don't unnecessarily import the
  4. PackageFinder machinery and all its vendored dependencies, etc.
  5. """
  6. # The following comment should be removed at some point in the future.
  7. # mypy: disallow-untyped-defs=False
  8. import os
  9. from functools import partial
  10. from pip._internal.cli.base_command import Command
  11. from pip._internal.cli.command_context import CommandContextMixIn
  12. from pip._internal.exceptions import CommandError
  13. from pip._internal.index import PackageFinder
  14. from pip._internal.legacy_resolve import Resolver
  15. from pip._internal.models.selection_prefs import SelectionPreferences
  16. from pip._internal.network.session import PipSession
  17. from pip._internal.operations.prepare import RequirementPreparer
  18. from pip._internal.req.constructors import (
  19. install_req_from_editable,
  20. install_req_from_line,
  21. install_req_from_req_string,
  22. )
  23. from pip._internal.req.req_file import parse_requirements
  24. from pip._internal.self_outdated_check import (
  25. make_link_collector,
  26. pip_self_version_check,
  27. )
  28. from pip._internal.utils.misc import normalize_path
  29. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  30. if MYPY_CHECK_RUNNING:
  31. from optparse import Values
  32. from typing import List, Optional, Tuple
  33. from pip._internal.cache import WheelCache
  34. from pip._internal.models.target_python import TargetPython
  35. from pip._internal.req.req_set import RequirementSet
  36. from pip._internal.req.req_tracker import RequirementTracker
  37. from pip._internal.utils.temp_dir import TempDirectory
  38. class SessionCommandMixin(CommandContextMixIn):
  39. """
  40. A class mixin for command classes needing _build_session().
  41. """
  42. def __init__(self):
  43. super(SessionCommandMixin, self).__init__()
  44. self._session = None # Optional[PipSession]
  45. @classmethod
  46. def _get_index_urls(cls, options):
  47. """Return a list of index urls from user-provided options."""
  48. index_urls = []
  49. if not getattr(options, "no_index", False):
  50. url = getattr(options, "index_url", None)
  51. if url:
  52. index_urls.append(url)
  53. urls = getattr(options, "extra_index_urls", None)
  54. if urls:
  55. index_urls.extend(urls)
  56. # Return None rather than an empty list
  57. return index_urls or None
  58. def get_default_session(self, options):
  59. # type: (Values) -> PipSession
  60. """Get a default-managed session."""
  61. if self._session is None:
  62. self._session = self.enter_context(self._build_session(options))
  63. return self._session
  64. def _build_session(self, options, retries=None, timeout=None):
  65. # type: (Values, Optional[int], Optional[int]) -> PipSession
  66. session = PipSession(
  67. cache=(
  68. normalize_path(os.path.join(options.cache_dir, "http"))
  69. if options.cache_dir else None
  70. ),
  71. retries=retries if retries is not None else options.retries,
  72. trusted_hosts=options.trusted_hosts,
  73. index_urls=self._get_index_urls(options),
  74. )
  75. # Handle custom ca-bundles from the user
  76. if options.cert:
  77. session.verify = options.cert
  78. # Handle SSL client certificate
  79. if options.client_cert:
  80. session.cert = options.client_cert
  81. # Handle timeouts
  82. if options.timeout or timeout:
  83. session.timeout = (
  84. timeout if timeout is not None else options.timeout
  85. )
  86. # Handle configured proxies
  87. if options.proxy:
  88. session.proxies = {
  89. "http": options.proxy,
  90. "https": options.proxy,
  91. }
  92. # Determine if we can prompt the user for authentication or not
  93. session.auth.prompting = not options.no_input
  94. return session
  95. class IndexGroupCommand(Command, SessionCommandMixin):
  96. """
  97. Abstract base class for commands with the index_group options.
  98. This also corresponds to the commands that permit the pip version check.
  99. """
  100. def handle_pip_version_check(self, options):
  101. # type: (Values) -> None
  102. """
  103. Do the pip version check if not disabled.
  104. This overrides the default behavior of not doing the check.
  105. """
  106. # Make sure the index_group options are present.
  107. assert hasattr(options, 'no_index')
  108. if options.disable_pip_version_check or options.no_index:
  109. return
  110. # Otherwise, check if we're using the latest version of pip available.
  111. session = self._build_session(
  112. options,
  113. retries=0,
  114. timeout=min(5, options.timeout)
  115. )
  116. with session:
  117. pip_self_version_check(session, options)
  118. class RequirementCommand(IndexGroupCommand):
  119. @staticmethod
  120. def make_requirement_preparer(
  121. temp_build_dir, # type: TempDirectory
  122. options, # type: Values
  123. req_tracker, # type: RequirementTracker
  124. download_dir=None, # type: str
  125. wheel_download_dir=None, # type: str
  126. ):
  127. # type: (...) -> RequirementPreparer
  128. """
  129. Create a RequirementPreparer instance for the given parameters.
  130. """
  131. temp_build_dir_path = temp_build_dir.path
  132. assert temp_build_dir_path is not None
  133. return RequirementPreparer(
  134. build_dir=temp_build_dir_path,
  135. src_dir=options.src_dir,
  136. download_dir=download_dir,
  137. wheel_download_dir=wheel_download_dir,
  138. progress_bar=options.progress_bar,
  139. build_isolation=options.build_isolation,
  140. req_tracker=req_tracker,
  141. )
  142. @staticmethod
  143. def make_resolver(
  144. preparer, # type: RequirementPreparer
  145. session, # type: PipSession
  146. finder, # type: PackageFinder
  147. options, # type: Values
  148. wheel_cache=None, # type: Optional[WheelCache]
  149. use_user_site=False, # type: bool
  150. ignore_installed=True, # type: bool
  151. ignore_requires_python=False, # type: bool
  152. force_reinstall=False, # type: bool
  153. upgrade_strategy="to-satisfy-only", # type: str
  154. use_pep517=None, # type: Optional[bool]
  155. py_version_info=None # type: Optional[Tuple[int, ...]]
  156. ):
  157. # type: (...) -> Resolver
  158. """
  159. Create a Resolver instance for the given parameters.
  160. """
  161. make_install_req = partial(
  162. install_req_from_req_string,
  163. isolated=options.isolated_mode,
  164. wheel_cache=wheel_cache,
  165. use_pep517=use_pep517,
  166. )
  167. return Resolver(
  168. preparer=preparer,
  169. session=session,
  170. finder=finder,
  171. make_install_req=make_install_req,
  172. use_user_site=use_user_site,
  173. ignore_dependencies=options.ignore_dependencies,
  174. ignore_installed=ignore_installed,
  175. ignore_requires_python=ignore_requires_python,
  176. force_reinstall=force_reinstall,
  177. upgrade_strategy=upgrade_strategy,
  178. py_version_info=py_version_info
  179. )
  180. def populate_requirement_set(
  181. self,
  182. requirement_set, # type: RequirementSet
  183. args, # type: List[str]
  184. options, # type: Values
  185. finder, # type: PackageFinder
  186. session, # type: PipSession
  187. wheel_cache, # type: Optional[WheelCache]
  188. ):
  189. # type: (...) -> None
  190. """
  191. Marshal cmd line args into a requirement set.
  192. """
  193. # NOTE: As a side-effect, options.require_hashes and
  194. # requirement_set.require_hashes may be updated
  195. for filename in options.constraints:
  196. for req_to_add in parse_requirements(
  197. filename,
  198. constraint=True, finder=finder, options=options,
  199. session=session, wheel_cache=wheel_cache):
  200. req_to_add.is_direct = True
  201. requirement_set.add_requirement(req_to_add)
  202. for req in args:
  203. req_to_add = install_req_from_line(
  204. req, None, isolated=options.isolated_mode,
  205. use_pep517=options.use_pep517,
  206. wheel_cache=wheel_cache
  207. )
  208. req_to_add.is_direct = True
  209. requirement_set.add_requirement(req_to_add)
  210. for req in options.editables:
  211. req_to_add = install_req_from_editable(
  212. req,
  213. isolated=options.isolated_mode,
  214. use_pep517=options.use_pep517,
  215. wheel_cache=wheel_cache
  216. )
  217. req_to_add.is_direct = True
  218. requirement_set.add_requirement(req_to_add)
  219. for filename in options.requirements:
  220. for req_to_add in parse_requirements(
  221. filename,
  222. finder=finder, options=options, session=session,
  223. wheel_cache=wheel_cache,
  224. use_pep517=options.use_pep517):
  225. req_to_add.is_direct = True
  226. requirement_set.add_requirement(req_to_add)
  227. # If --require-hashes was a line in a requirements file, tell
  228. # RequirementSet about it:
  229. requirement_set.require_hashes = options.require_hashes
  230. if not (args or options.editables or options.requirements):
  231. opts = {'name': self.name}
  232. if options.find_links:
  233. raise CommandError(
  234. 'You must give at least one requirement to %(name)s '
  235. '(maybe you meant "pip %(name)s %(links)s"?)' %
  236. dict(opts, links=' '.join(options.find_links)))
  237. else:
  238. raise CommandError(
  239. 'You must give at least one requirement to %(name)s '
  240. '(see "pip help %(name)s")' % opts)
  241. def _build_package_finder(
  242. self,
  243. options, # type: Values
  244. session, # type: PipSession
  245. target_python=None, # type: Optional[TargetPython]
  246. ignore_requires_python=None, # type: Optional[bool]
  247. ):
  248. # type: (...) -> PackageFinder
  249. """
  250. Create a package finder appropriate to this requirement command.
  251. :param ignore_requires_python: Whether to ignore incompatible
  252. "Requires-Python" values in links. Defaults to False.
  253. """
  254. link_collector = make_link_collector(session, options=options)
  255. selection_prefs = SelectionPreferences(
  256. allow_yanked=True,
  257. format_control=options.format_control,
  258. allow_all_prereleases=options.pre,
  259. prefer_binary=options.prefer_binary,
  260. ignore_requires_python=ignore_requires_python,
  261. )
  262. return PackageFinder.create(
  263. link_collector=link_collector,
  264. selection_prefs=selection_prefs,
  265. target_python=target_python,
  266. )