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