index_command.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. """
  2. Contains command classes which may interact with an index / the network.
  3. Unlike its sister module, req_command, this module still uses lazy imports
  4. so commands which don't always hit the network (e.g. list w/o --outdated or
  5. --uptodate) don't need waste time importing PipSession and friends.
  6. """
  7. from __future__ import annotations
  8. import logging
  9. import os
  10. import sys
  11. from functools import lru_cache
  12. from optparse import Values
  13. from typing import TYPE_CHECKING
  14. from pip._vendor import certifi
  15. from pip._internal.cli.base_command import Command
  16. from pip._internal.cli.command_context import CommandContextMixIn
  17. if TYPE_CHECKING:
  18. from ssl import SSLContext
  19. from pip._vendor.packaging.utils import NormalizedName
  20. from pip._internal.network.session import PipSession
  21. logger = logging.getLogger(__name__)
  22. @lru_cache
  23. def _create_truststore_ssl_context() -> SSLContext | None:
  24. if sys.version_info < (3, 10):
  25. logger.debug("Disabling truststore because Python version isn't 3.10+")
  26. return None
  27. try:
  28. import ssl
  29. except ImportError:
  30. logger.warning("Disabling truststore since ssl support is missing")
  31. return None
  32. try:
  33. from pip._vendor import truststore
  34. except ImportError:
  35. logger.warning("Disabling truststore because platform isn't supported")
  36. return None
  37. ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
  38. ctx.load_verify_locations(certifi.where())
  39. return ctx
  40. class SessionCommandMixin(CommandContextMixIn):
  41. """
  42. A class mixin for command classes needing _build_session().
  43. """
  44. def __init__(self) -> None:
  45. super().__init__()
  46. self._session: PipSession | None = None
  47. @classmethod
  48. def _get_index_urls(cls, options: Values) -> list[str] | None:
  49. """Return a list of index urls from user-provided options."""
  50. index_urls = []
  51. if not getattr(options, "no_index", False):
  52. url = getattr(options, "index_url", None)
  53. if url:
  54. index_urls.append(url)
  55. urls = getattr(options, "extra_index_urls", None)
  56. if urls:
  57. index_urls.extend(urls)
  58. # Return None rather than an empty list
  59. return index_urls or None
  60. def get_default_session(self, options: Values) -> PipSession:
  61. """Get a default-managed session."""
  62. if self._session is None:
  63. self._session = self.enter_context(self._build_session(options))
  64. # there's no type annotation on requests.Session, so it's
  65. # automatically ContextManager[Any] and self._session becomes Any,
  66. # then https://github.com/python/mypy/issues/7696 kicks in
  67. assert self._session is not None
  68. return self._session
  69. def _build_session(
  70. self,
  71. options: Values,
  72. retries: int | None = None,
  73. timeout: int | None = None,
  74. ) -> PipSession:
  75. from pip._internal.network.session import PipSession
  76. cache_dir = options.cache_dir
  77. assert not cache_dir or os.path.isabs(cache_dir)
  78. if "legacy-certs" not in options.deprecated_features_enabled:
  79. ssl_context = _create_truststore_ssl_context()
  80. else:
  81. ssl_context = None
  82. session = PipSession(
  83. cache=os.path.join(cache_dir, "http-v2") if cache_dir else None,
  84. retries=retries if retries is not None else options.retries,
  85. resume_retries=options.resume_retries,
  86. trusted_hosts=options.trusted_hosts,
  87. index_urls=self._get_index_urls(options),
  88. ssl_context=ssl_context,
  89. )
  90. # Handle custom ca-bundles from the user
  91. if options.cert:
  92. session.verify = options.cert
  93. # Handle SSL client certificate
  94. if options.client_cert:
  95. session.cert = options.client_cert
  96. # Handle timeouts
  97. if options.timeout or timeout:
  98. session.timeout = timeout if timeout is not None else options.timeout
  99. # Handle configured proxies
  100. if options.proxy:
  101. session.proxies = {
  102. "http": options.proxy,
  103. "https": options.proxy,
  104. }
  105. session.trust_env = False
  106. session.pip_proxy = options.proxy
  107. # Determine if we can prompt the user for authentication or not
  108. session.auth.prompting = not options.no_input
  109. session.auth.keyring_provider = options.keyring_provider
  110. return session
  111. def _pip_self_version_check(session: PipSession, options: Values) -> None:
  112. from pip._internal.self_outdated_check import pip_self_version_check as check
  113. check(session, options)
  114. class IndexGroupCommand(Command, SessionCommandMixin):
  115. """
  116. Abstract base class for commands with the index_group options.
  117. This also corresponds to the commands that permit the pip version check.
  118. """
  119. def should_exclude_prerelease(
  120. self, options: Values, package_name: NormalizedName
  121. ) -> bool:
  122. """
  123. Determine if pre-releases should be excluded for a package.
  124. """
  125. # Check per-package release control settings
  126. if options.release_control:
  127. allow_prereleases = options.release_control.allows_prereleases(package_name)
  128. if allow_prereleases is True:
  129. return False # Include pre-releases
  130. elif allow_prereleases is False:
  131. return True # Exclude pre-releases
  132. # No specific setting: exclude prereleases by default
  133. return True
  134. def handle_pip_version_check(self, options: Values) -> None:
  135. """
  136. Do the pip version check if not disabled.
  137. This overrides the default behavior of not doing the check.
  138. """
  139. # Make sure the index_group options are present.
  140. assert hasattr(options, "no_index")
  141. if options.disable_pip_version_check or options.no_index:
  142. return
  143. try:
  144. # Otherwise, check if we're using the latest version of pip available.
  145. session = self._build_session(
  146. options,
  147. retries=0,
  148. timeout=min(5, options.timeout),
  149. )
  150. with session:
  151. _pip_self_version_check(session, options)
  152. except Exception:
  153. logger.warning("There was an error checking the latest version of pip.")
  154. logger.debug("See below for error", exc_info=True)