(root)/
Python-3.12.0/
Lib/
ensurepip/
__init__.py
       1  import collections
       2  import os
       3  import os.path
       4  import subprocess
       5  import sys
       6  import sysconfig
       7  import tempfile
       8  from importlib import resources
       9  
      10  
      11  __all__ = ["version", "bootstrap"]
      12  _PACKAGE_NAMES = ('pip',)
      13  _PIP_VERSION = "23.2.1"
      14  _PROJECTS = [
      15      ("pip", _PIP_VERSION, "py3"),
      16  ]
      17  
      18  # Packages bundled in ensurepip._bundled have wheel_name set.
      19  # Packages from WHEEL_PKG_DIR have wheel_path set.
      20  _Package = collections.namedtuple('Package',
      21                                    ('version', 'wheel_name', 'wheel_path'))
      22  
      23  # Directory of system wheel packages. Some Linux distribution packaging
      24  # policies recommend against bundling dependencies. For example, Fedora
      25  # installs wheel packages in the /usr/share/python-wheels/ directory and don't
      26  # install the ensurepip._bundled package.
      27  _WHEEL_PKG_DIR = sysconfig.get_config_var('WHEEL_PKG_DIR')
      28  
      29  
      30  def _find_packages(path):
      31      packages = {}
      32      try:
      33          filenames = os.listdir(path)
      34      except OSError:
      35          # Ignore: path doesn't exist or permission error
      36          filenames = ()
      37      # Make the code deterministic if a directory contains multiple wheel files
      38      # of the same package, but don't attempt to implement correct version
      39      # comparison since this case should not happen.
      40      filenames = sorted(filenames)
      41      for filename in filenames:
      42          # filename is like 'pip-21.2.4-py3-none-any.whl'
      43          if not filename.endswith(".whl"):
      44              continue
      45          for name in _PACKAGE_NAMES:
      46              prefix = name + '-'
      47              if filename.startswith(prefix):
      48                  break
      49          else:
      50              continue
      51  
      52          # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl'
      53          version = filename.removeprefix(prefix).partition('-')[0]
      54          wheel_path = os.path.join(path, filename)
      55          packages[name] = _Package(version, None, wheel_path)
      56      return packages
      57  
      58  
      59  def _get_packages():
      60      global _PACKAGES, _WHEEL_PKG_DIR
      61      if _PACKAGES is not None:
      62          return _PACKAGES
      63  
      64      packages = {}
      65      for name, version, py_tag in _PROJECTS:
      66          wheel_name = f"{name}-{version}-{py_tag}-none-any.whl"
      67          packages[name] = _Package(version, wheel_name, None)
      68      if _WHEEL_PKG_DIR:
      69          dir_packages = _find_packages(_WHEEL_PKG_DIR)
      70          # only used the wheel package directory if all packages are found there
      71          if all(name in dir_packages for name in _PACKAGE_NAMES):
      72              packages = dir_packages
      73      _PACKAGES = packages
      74      return packages
      75  _PACKAGES = None
      76  
      77  
      78  def _run_pip(args, additional_paths=None):
      79      # Run the bootstrapping in a subprocess to avoid leaking any state that happens
      80      # after pip has executed. Particularly, this avoids the case when pip holds onto
      81      # the files in *additional_paths*, preventing us to remove them at the end of the
      82      # invocation.
      83      code = f"""
      84  import runpy
      85  import sys
      86  sys.path = {additional_paths or []} + sys.path
      87  sys.argv[1:] = {args}
      88  runpy.run_module("pip", run_name="__main__", alter_sys=True)
      89  """
      90  
      91      cmd = [
      92          sys.executable,
      93          '-W',
      94          'ignore::DeprecationWarning',
      95          '-c',
      96          code,
      97      ]
      98      if sys.flags.isolated:
      99          # run code in isolated mode if currently running isolated
     100          cmd.insert(1, '-I')
     101      return subprocess.run(cmd, check=True).returncode
     102  
     103  
     104  def version():
     105      """
     106      Returns a string specifying the bundled version of pip.
     107      """
     108      return _get_packages()['pip'].version
     109  
     110  
     111  def _disable_pip_configuration_settings():
     112      # We deliberately ignore all pip environment variables
     113      # when invoking pip
     114      # See http://bugs.python.org/issue19734 for details
     115      keys_to_remove = [k for k in os.environ if k.startswith("PIP_")]
     116      for k in keys_to_remove:
     117          del os.environ[k]
     118      # We also ignore the settings in the default pip configuration file
     119      # See http://bugs.python.org/issue20053 for details
     120      os.environ['PIP_CONFIG_FILE'] = os.devnull
     121  
     122  
     123  def bootstrap(*, root=None, upgrade=False, user=False,
     124                altinstall=False, default_pip=False,
     125                verbosity=0):
     126      """
     127      Bootstrap pip into the current Python installation (or the given root
     128      directory).
     129  
     130      Note that calling this function will alter both sys.path and os.environ.
     131      """
     132      # Discard the return value
     133      _bootstrap(root=root, upgrade=upgrade, user=user,
     134                 altinstall=altinstall, default_pip=default_pip,
     135                 verbosity=verbosity)
     136  
     137  
     138  def _bootstrap(*, root=None, upgrade=False, user=False,
     139                altinstall=False, default_pip=False,
     140                verbosity=0):
     141      """
     142      Bootstrap pip into the current Python installation (or the given root
     143      directory). Returns pip command status code.
     144  
     145      Note that calling this function will alter both sys.path and os.environ.
     146      """
     147      if altinstall and default_pip:
     148          raise ValueError("Cannot use altinstall and default_pip together")
     149  
     150      sys.audit("ensurepip.bootstrap", root)
     151  
     152      _disable_pip_configuration_settings()
     153  
     154      # By default, installing pip installs all of the
     155      # following scripts (X.Y == running Python version):
     156      #
     157      #   pip, pipX, pipX.Y
     158      #
     159      # pip 1.5+ allows ensurepip to request that some of those be left out
     160      if altinstall:
     161          # omit pip, pipX
     162          os.environ["ENSUREPIP_OPTIONS"] = "altinstall"
     163      elif not default_pip:
     164          # omit pip
     165          os.environ["ENSUREPIP_OPTIONS"] = "install"
     166  
     167      with tempfile.TemporaryDirectory() as tmpdir:
     168          # Put our bundled wheels into a temporary directory and construct the
     169          # additional paths that need added to sys.path
     170          additional_paths = []
     171          for name, package in _get_packages().items():
     172              if package.wheel_name:
     173                  # Use bundled wheel package
     174                  wheel_name = package.wheel_name
     175                  wheel_path = resources.files("ensurepip") / "_bundled" / wheel_name
     176                  whl = wheel_path.read_bytes()
     177              else:
     178                  # Use the wheel package directory
     179                  with open(package.wheel_path, "rb") as fp:
     180                      whl = fp.read()
     181                  wheel_name = os.path.basename(package.wheel_path)
     182  
     183              filename = os.path.join(tmpdir, wheel_name)
     184              with open(filename, "wb") as fp:
     185                  fp.write(whl)
     186  
     187              additional_paths.append(filename)
     188  
     189          # Construct the arguments to be passed to the pip command
     190          args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
     191          if root:
     192              args += ["--root", root]
     193          if upgrade:
     194              args += ["--upgrade"]
     195          if user:
     196              args += ["--user"]
     197          if verbosity:
     198              args += ["-" + "v" * verbosity]
     199  
     200          return _run_pip([*args, *_PACKAGE_NAMES], additional_paths)
     201  
     202  def _uninstall_helper(*, verbosity=0):
     203      """Helper to support a clean default uninstall process on Windows
     204  
     205      Note that calling this function may alter os.environ.
     206      """
     207      # Nothing to do if pip was never installed, or has been removed
     208      try:
     209          import pip
     210      except ImportError:
     211          return
     212  
     213      # If the installed pip version doesn't match the available one,
     214      # leave it alone
     215      available_version = version()
     216      if pip.__version__ != available_version:
     217          print(f"ensurepip will only uninstall a matching version "
     218                f"({pip.__version__!r} installed, "
     219                f"{available_version!r} available)",
     220                file=sys.stderr)
     221          return
     222  
     223      _disable_pip_configuration_settings()
     224  
     225      # Construct the arguments to be passed to the pip command
     226      args = ["uninstall", "-y", "--disable-pip-version-check"]
     227      if verbosity:
     228          args += ["-" + "v" * verbosity]
     229  
     230      return _run_pip([*args, *reversed(_PACKAGE_NAMES)])
     231  
     232  
     233  def _main(argv=None):
     234      import argparse
     235      parser = argparse.ArgumentParser(prog="python -m ensurepip")
     236      parser.add_argument(
     237          "--version",
     238          action="version",
     239          version="pip {}".format(version()),
     240          help="Show the version of pip that is bundled with this Python.",
     241      )
     242      parser.add_argument(
     243          "-v", "--verbose",
     244          action="count",
     245          default=0,
     246          dest="verbosity",
     247          help=("Give more output. Option is additive, and can be used up to 3 "
     248                "times."),
     249      )
     250      parser.add_argument(
     251          "-U", "--upgrade",
     252          action="store_true",
     253          default=False,
     254          help="Upgrade pip and dependencies, even if already installed.",
     255      )
     256      parser.add_argument(
     257          "--user",
     258          action="store_true",
     259          default=False,
     260          help="Install using the user scheme.",
     261      )
     262      parser.add_argument(
     263          "--root",
     264          default=None,
     265          help="Install everything relative to this alternate root directory.",
     266      )
     267      parser.add_argument(
     268          "--altinstall",
     269          action="store_true",
     270          default=False,
     271          help=("Make an alternate install, installing only the X.Y versioned "
     272                "scripts (Default: pipX, pipX.Y)."),
     273      )
     274      parser.add_argument(
     275          "--default-pip",
     276          action="store_true",
     277          default=False,
     278          help=("Make a default pip install, installing the unqualified pip "
     279                "in addition to the versioned scripts."),
     280      )
     281  
     282      args = parser.parse_args(argv)
     283  
     284      return _bootstrap(
     285          root=args.root,
     286          upgrade=args.upgrade,
     287          user=args.user,
     288          verbosity=args.verbosity,
     289          altinstall=args.altinstall,
     290          default_pip=args.default_pip,
     291      )