__init__.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. from __future__ import annotations
  2. import contextlib
  3. import functools
  4. import os
  5. import sys
  6. from typing import TYPE_CHECKING, Literal, Protocol, cast
  7. from pip._internal.utils.deprecation import deprecated
  8. from pip._internal.utils.misc import strtobool
  9. from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
  10. if TYPE_CHECKING:
  11. from pip._vendor.packaging.utils import NormalizedName
  12. __all__ = [
  13. "BaseDistribution",
  14. "BaseEnvironment",
  15. "FilesystemWheel",
  16. "MemoryWheel",
  17. "Wheel",
  18. "get_default_environment",
  19. "get_environment",
  20. "get_wheel_distribution",
  21. "select_backend",
  22. ]
  23. def _should_use_importlib_metadata() -> bool:
  24. """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
  25. By default, pip uses ``importlib.metadata`` on Python 3.11+, and
  26. ``pkg_resources`` otherwise. Up to Python 3.13, This can be
  27. overridden by a couple of ways:
  28. * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
  29. dictates whether ``importlib.metadata`` is used, for Python <3.14.
  30. * On Python 3.11, 3.12 and 3.13, Python distributors can patch
  31. ``importlib.metadata`` to add a global constant
  32. ``_PIP_USE_IMPORTLIB_METADATA = False``. This makes pip use
  33. ``pkg_resources`` (unless the user set the aforementioned environment
  34. variable to *True*).
  35. On Python 3.14+, the ``pkg_resources`` backend cannot be used.
  36. """
  37. if sys.version_info >= (3, 14):
  38. # On Python >=3.14 we only support importlib.metadata.
  39. return True
  40. with contextlib.suppress(KeyError, ValueError):
  41. # On Python <3.14, if the environment variable is set, we obey what it says.
  42. return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"]))
  43. if sys.version_info < (3, 11):
  44. # On Python <3.11, we always use pkg_resources, unless the environment
  45. # variable was set.
  46. return False
  47. # On Python 3.11, 3.12 and 3.13, we check if the global constant is set.
  48. import importlib.metadata
  49. return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True))
  50. def _emit_pkg_resources_deprecation_if_needed() -> None:
  51. if sys.version_info < (3, 11):
  52. # All pip versions supporting Python<=3.11 will support pkg_resources,
  53. # and pkg_resources is the default for these, so let's not bother users.
  54. return
  55. import importlib.metadata
  56. if hasattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA"):
  57. # The Python distributor has set the global constant, so we don't
  58. # warn, since it is not a user decision.
  59. return
  60. # The user has decided to use pkg_resources, so we warn.
  61. deprecated(
  62. reason="Using the pkg_resources metadata backend is deprecated.",
  63. replacement=(
  64. "to use the default importlib.metadata backend, "
  65. "by unsetting the _PIP_USE_IMPORTLIB_METADATA environment variable"
  66. ),
  67. gone_in="26.3",
  68. issue=13317,
  69. )
  70. class Backend(Protocol):
  71. NAME: Literal["importlib", "pkg_resources"]
  72. Distribution: type[BaseDistribution]
  73. Environment: type[BaseEnvironment]
  74. @functools.cache
  75. def select_backend() -> Backend:
  76. if _should_use_importlib_metadata():
  77. from . import importlib
  78. return cast(Backend, importlib)
  79. _emit_pkg_resources_deprecation_if_needed()
  80. from . import pkg_resources
  81. return cast(Backend, pkg_resources)
  82. def get_default_environment() -> BaseEnvironment:
  83. """Get the default representation for the current environment.
  84. This returns an Environment instance from the chosen backend. The default
  85. Environment instance should be built from ``sys.path`` and may use caching
  86. to share instance state across calls.
  87. """
  88. return select_backend().Environment.default()
  89. def get_environment(paths: list[str] | None) -> BaseEnvironment:
  90. """Get a representation of the environment specified by ``paths``.
  91. This returns an Environment instance from the chosen backend based on the
  92. given import paths. The backend must build a fresh instance representing
  93. the state of installed distributions when this function is called.
  94. """
  95. return select_backend().Environment.from_paths(paths)
  96. def get_directory_distribution(directory: str) -> BaseDistribution:
  97. """Get the distribution metadata representation in the specified directory.
  98. This returns a Distribution instance from the chosen backend based on
  99. the given on-disk ``.dist-info`` directory.
  100. """
  101. return select_backend().Distribution.from_directory(directory)
  102. def get_wheel_distribution(
  103. wheel: Wheel, canonical_name: NormalizedName
  104. ) -> BaseDistribution:
  105. """Get the representation of the specified wheel's distribution metadata.
  106. This returns a Distribution instance from the chosen backend based on
  107. the given wheel's ``.dist-info`` directory.
  108. :param canonical_name: Normalized project name of the given wheel.
  109. """
  110. return select_backend().Distribution.from_wheel(wheel, canonical_name)
  111. def get_metadata_distribution(
  112. metadata_contents: bytes,
  113. filename: str,
  114. canonical_name: str,
  115. ) -> BaseDistribution:
  116. """Get the dist representation of the specified METADATA file contents.
  117. This returns a Distribution instance from the chosen backend sourced from the data
  118. in `metadata_contents`.
  119. :param metadata_contents: Contents of a METADATA file within a dist, or one served
  120. via PEP 658.
  121. :param filename: Filename for the dist this metadata represents.
  122. :param canonical_name: Normalized project name of the given dist.
  123. """
  124. return select_backend().Distribution.from_metadata_file_contents(
  125. metadata_contents,
  126. filename,
  127. canonical_name,
  128. )