req_command.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  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. import logging
  7. import os
  8. from functools import partial
  9. from pip._internal.cli import cmdoptions
  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, PreviousBuildDirError
  13. from pip._internal.index.package_finder import PackageFinder
  14. from pip._internal.models.selection_prefs import SelectionPreferences
  15. from pip._internal.network.download import Downloader
  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_parsed_requirement,
  22. install_req_from_req_string,
  23. )
  24. from pip._internal.req.req_file import parse_requirements
  25. from pip._internal.req.req_set import RequirementSet
  26. from pip._internal.self_outdated_check import (
  27. make_link_collector,
  28. pip_self_version_check,
  29. )
  30. from pip._internal.utils.temp_dir import tempdir_kinds
  31. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  32. if MYPY_CHECK_RUNNING:
  33. from optparse import Values
  34. from typing import Any, List, Optional, Tuple
  35. from pip._internal.cache import WheelCache
  36. from pip._internal.models.target_python import TargetPython
  37. from pip._internal.req.req_install import InstallRequirement
  38. from pip._internal.req.req_tracker import RequirementTracker
  39. from pip._internal.resolution.base import BaseResolver
  40. from pip._internal.utils.temp_dir import (
  41. TempDirectory,
  42. TempDirectoryTypeRegistry,
  43. )
  44. logger = logging.getLogger(__name__)
  45. class SessionCommandMixin(CommandContextMixIn):
  46. """
  47. A class mixin for command classes needing _build_session().
  48. """
  49. def __init__(self):
  50. # type: () -> None
  51. super(SessionCommandMixin, self).__init__()
  52. self._session = None # Optional[PipSession]
  53. @classmethod
  54. def _get_index_urls(cls, options):
  55. # type: (Values) -> Optional[List[str]]
  56. """Return a list of index urls from user-provided options."""
  57. index_urls = []
  58. if not getattr(options, "no_index", False):
  59. url = getattr(options, "index_url", None)
  60. if url:
  61. index_urls.append(url)
  62. urls = getattr(options, "extra_index_urls", None)
  63. if urls:
  64. index_urls.extend(urls)
  65. # Return None rather than an empty list
  66. return index_urls or None
  67. def get_default_session(self, options):
  68. # type: (Values) -> PipSession
  69. """Get a default-managed session."""
  70. if self._session is None:
  71. self._session = self.enter_context(self._build_session(options))
  72. # there's no type annotation on requests.Session, so it's
  73. # automatically ContextManager[Any] and self._session becomes Any,
  74. # then https://github.com/python/mypy/issues/7696 kicks in
  75. assert self._session is not None
  76. return self._session
  77. def _build_session(self, options, retries=None, timeout=None):
  78. # type: (Values, Optional[int], Optional[int]) -> PipSession
  79. assert not options.cache_dir or os.path.isabs(options.cache_dir)
  80. session = PipSession(
  81. cache=(
  82. os.path.join(options.cache_dir, "http")
  83. if options.cache_dir else None
  84. ),
  85. retries=retries if retries is not None else options.retries,
  86. trusted_hosts=options.trusted_hosts,
  87. index_urls=self._get_index_urls(options),
  88. )
  89. # Handle custom ca-bundles from the user
  90. if options.cert:
  91. session.verify = options.cert
  92. # Handle SSL client certificate
  93. if options.client_cert:
  94. session.cert = options.client_cert
  95. # Handle timeouts
  96. if options.timeout or timeout:
  97. session.timeout = (
  98. timeout if timeout is not None else options.timeout
  99. )
  100. # Handle configured proxies
  101. if options.proxy:
  102. session.proxies = {
  103. "http": options.proxy,
  104. "https": options.proxy,
  105. }
  106. # Determine if we can prompt the user for authentication or not
  107. session.auth.prompting = not options.no_input
  108. return session
  109. class IndexGroupCommand(Command, SessionCommandMixin):
  110. """
  111. Abstract base class for commands with the index_group options.
  112. This also corresponds to the commands that permit the pip version check.
  113. """
  114. def handle_pip_version_check(self, options):
  115. # type: (Values) -> None
  116. """
  117. Do the pip version check if not disabled.
  118. This overrides the default behavior of not doing the check.
  119. """
  120. # Make sure the index_group options are present.
  121. assert hasattr(options, 'no_index')
  122. if options.disable_pip_version_check or options.no_index:
  123. return
  124. # Otherwise, check if we're using the latest version of pip available.
  125. session = self._build_session(
  126. options,
  127. retries=0,
  128. timeout=min(5, options.timeout)
  129. )
  130. with session:
  131. pip_self_version_check(session, options)
  132. KEEPABLE_TEMPDIR_TYPES = [
  133. tempdir_kinds.BUILD_ENV,
  134. tempdir_kinds.EPHEM_WHEEL_CACHE,
  135. tempdir_kinds.REQ_BUILD,
  136. ]
  137. def with_cleanup(func):
  138. # type: (Any) -> Any
  139. """Decorator for common logic related to managing temporary
  140. directories.
  141. """
  142. def configure_tempdir_registry(registry):
  143. # type: (TempDirectoryTypeRegistry) -> None
  144. for t in KEEPABLE_TEMPDIR_TYPES:
  145. registry.set_delete(t, False)
  146. def wrapper(self, options, args):
  147. # type: (RequirementCommand, Values, List[Any]) -> Optional[int]
  148. assert self.tempdir_registry is not None
  149. if options.no_clean:
  150. configure_tempdir_registry(self.tempdir_registry)
  151. try:
  152. return func(self, options, args)
  153. except PreviousBuildDirError:
  154. # This kind of conflict can occur when the user passes an explicit
  155. # build directory with a pre-existing folder. In that case we do
  156. # not want to accidentally remove it.
  157. configure_tempdir_registry(self.tempdir_registry)
  158. raise
  159. return wrapper
  160. class RequirementCommand(IndexGroupCommand):
  161. def __init__(self, *args, **kw):
  162. # type: (Any, Any) -> None
  163. super(RequirementCommand, self).__init__(*args, **kw)
  164. self.cmd_opts.add_option(cmdoptions.no_clean())
  165. @staticmethod
  166. def make_requirement_preparer(
  167. temp_build_dir, # type: TempDirectory
  168. options, # type: Values
  169. req_tracker, # type: RequirementTracker
  170. session, # type: PipSession
  171. finder, # type: PackageFinder
  172. use_user_site, # type: bool
  173. download_dir=None, # type: str
  174. wheel_download_dir=None, # type: str
  175. ):
  176. # type: (...) -> RequirementPreparer
  177. """
  178. Create a RequirementPreparer instance for the given parameters.
  179. """
  180. downloader = Downloader(session, progress_bar=options.progress_bar)
  181. temp_build_dir_path = temp_build_dir.path
  182. assert temp_build_dir_path is not None
  183. return RequirementPreparer(
  184. build_dir=temp_build_dir_path,
  185. src_dir=options.src_dir,
  186. download_dir=download_dir,
  187. wheel_download_dir=wheel_download_dir,
  188. build_isolation=options.build_isolation,
  189. req_tracker=req_tracker,
  190. downloader=downloader,
  191. finder=finder,
  192. require_hashes=options.require_hashes,
  193. use_user_site=use_user_site,
  194. )
  195. @staticmethod
  196. def make_resolver(
  197. preparer, # type: RequirementPreparer
  198. finder, # type: PackageFinder
  199. options, # type: Values
  200. wheel_cache=None, # type: Optional[WheelCache]
  201. use_user_site=False, # type: bool
  202. ignore_installed=True, # type: bool
  203. ignore_requires_python=False, # type: bool
  204. force_reinstall=False, # type: bool
  205. upgrade_strategy="to-satisfy-only", # type: str
  206. use_pep517=None, # type: Optional[bool]
  207. py_version_info=None # type: Optional[Tuple[int, ...]]
  208. ):
  209. # type: (...) -> BaseResolver
  210. """
  211. Create a Resolver instance for the given parameters.
  212. """
  213. make_install_req = partial(
  214. install_req_from_req_string,
  215. isolated=options.isolated_mode,
  216. use_pep517=use_pep517,
  217. )
  218. # The long import name and duplicated invocation is needed to convince
  219. # Mypy into correctly typechecking. Otherwise it would complain the
  220. # "Resolver" class being redefined.
  221. if 'resolver' in options.unstable_features:
  222. import pip._internal.resolution.resolvelib.resolver
  223. return pip._internal.resolution.resolvelib.resolver.Resolver(
  224. preparer=preparer,
  225. finder=finder,
  226. wheel_cache=wheel_cache,
  227. make_install_req=make_install_req,
  228. use_user_site=use_user_site,
  229. ignore_dependencies=options.ignore_dependencies,
  230. ignore_installed=ignore_installed,
  231. ignore_requires_python=ignore_requires_python,
  232. force_reinstall=force_reinstall,
  233. upgrade_strategy=upgrade_strategy,
  234. py_version_info=py_version_info,
  235. )
  236. import pip._internal.resolution.legacy.resolver
  237. return pip._internal.resolution.legacy.resolver.Resolver(
  238. preparer=preparer,
  239. finder=finder,
  240. wheel_cache=wheel_cache,
  241. make_install_req=make_install_req,
  242. use_user_site=use_user_site,
  243. ignore_dependencies=options.ignore_dependencies,
  244. ignore_installed=ignore_installed,
  245. ignore_requires_python=ignore_requires_python,
  246. force_reinstall=force_reinstall,
  247. upgrade_strategy=upgrade_strategy,
  248. py_version_info=py_version_info,
  249. )
  250. def get_requirements(
  251. self,
  252. args, # type: List[str]
  253. options, # type: Values
  254. finder, # type: PackageFinder
  255. session, # type: PipSession
  256. check_supported_wheels=True, # type: bool
  257. ):
  258. # type: (...) -> List[InstallRequirement]
  259. """
  260. Parse command-line arguments into the corresponding requirements.
  261. """
  262. requirement_set = RequirementSet(
  263. check_supported_wheels=check_supported_wheels
  264. )
  265. for filename in options.constraints:
  266. for parsed_req in parse_requirements(
  267. filename,
  268. constraint=True, finder=finder, options=options,
  269. session=session):
  270. req_to_add = install_req_from_parsed_requirement(
  271. parsed_req,
  272. isolated=options.isolated_mode,
  273. )
  274. req_to_add.is_direct = True
  275. requirement_set.add_requirement(req_to_add)
  276. for req in args:
  277. req_to_add = install_req_from_line(
  278. req, None, isolated=options.isolated_mode,
  279. use_pep517=options.use_pep517,
  280. )
  281. req_to_add.is_direct = True
  282. requirement_set.add_requirement(req_to_add)
  283. for req in options.editables:
  284. req_to_add = install_req_from_editable(
  285. req,
  286. isolated=options.isolated_mode,
  287. use_pep517=options.use_pep517,
  288. )
  289. req_to_add.is_direct = True
  290. requirement_set.add_requirement(req_to_add)
  291. # NOTE: options.require_hashes may be set if --require-hashes is True
  292. for filename in options.requirements:
  293. for parsed_req in parse_requirements(
  294. filename,
  295. finder=finder, options=options, session=session):
  296. req_to_add = install_req_from_parsed_requirement(
  297. parsed_req,
  298. isolated=options.isolated_mode,
  299. use_pep517=options.use_pep517
  300. )
  301. req_to_add.is_direct = True
  302. requirement_set.add_requirement(req_to_add)
  303. # If any requirement has hash options, enable hash checking.
  304. requirements = requirement_set.all_requirements
  305. if any(req.has_hash_options for req in requirements):
  306. options.require_hashes = True
  307. if not (args or options.editables or options.requirements):
  308. opts = {'name': self.name}
  309. if options.find_links:
  310. raise CommandError(
  311. 'You must give at least one requirement to {name} '
  312. '(maybe you meant "pip {name} {links}"?)'.format(
  313. **dict(opts, links=' '.join(options.find_links))))
  314. else:
  315. raise CommandError(
  316. 'You must give at least one requirement to {name} '
  317. '(see "pip help {name}")'.format(**opts))
  318. return requirements
  319. @staticmethod
  320. def trace_basic_info(finder):
  321. # type: (PackageFinder) -> None
  322. """
  323. Trace basic information about the provided objects.
  324. """
  325. # Display where finder is looking for packages
  326. search_scope = finder.search_scope
  327. locations = search_scope.get_formatted_locations()
  328. if locations:
  329. logger.info(locations)
  330. def _build_package_finder(
  331. self,
  332. options, # type: Values
  333. session, # type: PipSession
  334. target_python=None, # type: Optional[TargetPython]
  335. ignore_requires_python=None, # type: Optional[bool]
  336. ):
  337. # type: (...) -> PackageFinder
  338. """
  339. Create a package finder appropriate to this requirement command.
  340. :param ignore_requires_python: Whether to ignore incompatible
  341. "Requires-Python" values in links. Defaults to False.
  342. """
  343. link_collector = make_link_collector(session, options=options)
  344. selection_prefs = SelectionPreferences(
  345. allow_yanked=True,
  346. format_control=options.format_control,
  347. allow_all_prereleases=options.pre,
  348. prefer_binary=options.prefer_binary,
  349. ignore_requires_python=ignore_requires_python,
  350. )
  351. return PackageFinder.create(
  352. link_collector=link_collector,
  353. selection_prefs=selection_prefs,
  354. target_python=target_python,
  355. )