Загрузить файлы в «venv/Lib/site-packages/pip/_internal/utils»

This commit is contained in:
2026-07-02 19:10:35 +00:00
parent d5c58e0a3b
commit c2a6b91c8a
5 changed files with 523 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
"""Generate and work with PEP 425 Compatibility Tags."""
from __future__ import annotations
import re
from pip._vendor.packaging.tags import (
PythonVersion,
Tag,
android_platforms,
compatible_tags,
cpython_tags,
generic_tags,
interpreter_name,
interpreter_version,
ios_platforms,
mac_platforms,
)
_apple_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
def version_info_to_nodot(version_info: tuple[int, ...]) -> str:
# Only use up to the first two numbers.
return "".join(map(str, version_info[:2]))
def _mac_platforms(arch: str) -> list[str]:
match = _apple_arch_pat.match(arch)
if match:
name, major, minor, actual_arch = match.groups()
mac_version = (int(major), int(minor))
arches = [
# Since we have always only checked that the platform starts
# with "macosx", for backwards-compatibility we extract the
# actual prefix provided by the user in case they provided
# something like "macosxcustom_". It may be good to remove
# this as undocumented or deprecate it in the future.
"{}_{}".format(name, arch[len("macosx_") :])
for arch in mac_platforms(mac_version, actual_arch)
]
else:
# arch pattern didn't match (?!)
arches = [arch]
return arches
def _ios_platforms(arch: str) -> list[str]:
match = _apple_arch_pat.match(arch)
if match:
name, major, minor, actual_multiarch = match.groups()
ios_version = (int(major), int(minor))
arches = [
# Since we have always only checked that the platform starts
# with "ios", for backwards-compatibility we extract the
# actual prefix provided by the user in case they provided
# something like "ioscustom_". It may be good to remove
# this as undocumented or deprecate it in the future.
"{}_{}".format(name, arch[len("ios_") :])
for arch in ios_platforms(ios_version, actual_multiarch)
]
else:
# arch pattern didn't match (?!)
arches = [arch]
return arches
def _android_platforms(arch: str) -> list[str]:
match = re.fullmatch(r"android_(\d+)_(.+)", arch)
if match:
api_level, abi = match.groups()
return list(android_platforms(int(api_level), abi))
else:
# arch pattern didn't match (?!)
return [arch]
def _custom_manylinux_platforms(arch: str) -> list[str]:
arches = [arch]
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
if arch_prefix == "manylinux2014":
# manylinux1/manylinux2010 wheels run on most manylinux2014 systems
# with the exception of wheels depending on ncurses. PEP 599 states
# manylinux1/manylinux2010 wheels should be considered
# manylinux2014 wheels:
# https://www.python.org/dev/peps/pep-0599/#backwards-compatibility-with-manylinux2010-wheels
if arch_suffix in {"i686", "x86_64"}:
arches.append("manylinux2010" + arch_sep + arch_suffix)
arches.append("manylinux1" + arch_sep + arch_suffix)
elif arch_prefix == "manylinux2010":
# manylinux1 wheels run on most manylinux2010 systems with the
# exception of wheels depending on ncurses. PEP 571 states
# manylinux1 wheels should be considered manylinux2010 wheels:
# https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels
arches.append("manylinux1" + arch_sep + arch_suffix)
return arches
def _get_custom_platforms(arch: str) -> list[str]:
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
if arch.startswith("macosx"):
arches = _mac_platforms(arch)
elif arch.startswith("ios"):
arches = _ios_platforms(arch)
elif arch_prefix == "android":
arches = _android_platforms(arch)
elif arch_prefix in ["manylinux2014", "manylinux2010"]:
arches = _custom_manylinux_platforms(arch)
else:
arches = [arch]
return arches
def _expand_allowed_platforms(platforms: list[str] | None) -> list[str] | None:
if not platforms:
return None
seen = set()
result = []
for p in platforms:
if p in seen:
continue
additions = [c for c in _get_custom_platforms(p) if c not in seen]
seen.update(additions)
result.extend(additions)
return result
def _get_python_version(version: str) -> PythonVersion:
if len(version) > 1:
return int(version[0]), int(version[1:])
else:
return (int(version[0]),)
def _get_custom_interpreter(
implementation: str | None = None, version: str | None = None
) -> str:
if implementation is None:
implementation = interpreter_name()
if version is None:
version = interpreter_version()
return f"{implementation}{version}"
def get_supported(
version: str | None = None,
platforms: list[str] | None = None,
impl: str | None = None,
abis: list[str] | None = None,
) -> list[Tag]:
"""Return a list of supported tags for each version specified in
`versions`.
:param version: a string version, of the form "33" or "32",
or None. The version will be assumed to support our ABI.
:param platform: specify a list of platforms you want valid
tags for, or None. If None, use the local system platform.
:param impl: specify the exact implementation you want valid
tags for, or None. If None, use the local interpreter impl.
:param abis: specify a list of abis you want valid
tags for, or None. If None, use the local interpreter abi.
"""
supported: list[Tag] = []
python_version: PythonVersion | None = None
if version is not None:
python_version = _get_python_version(version)
interpreter = _get_custom_interpreter(impl, version)
platforms = _expand_allowed_platforms(platforms)
is_cpython = (impl or interpreter_name()) == "cp"
if is_cpython:
supported.extend(
cpython_tags(
python_version=python_version,
abis=abis,
platforms=platforms,
)
)
else:
supported.extend(
generic_tags(
interpreter=interpreter,
abis=abis,
platforms=platforms,
)
)
supported.extend(
compatible_tags(
python_version=python_version,
interpreter=interpreter,
platforms=platforms,
)
)
return supported

View File

@@ -0,0 +1,28 @@
"""For when pip wants to check the date or time."""
import datetime
import sys
def today_is_later_than(year: int, month: int, day: int) -> bool:
today = datetime.date.today()
given = datetime.date(year, month, day)
return today > given
def parse_iso_datetime(isodate: str) -> datetime.datetime:
"""Convert an ISO format string to a datetime.
Handles the format 2020-01-22T14:24:01Z (trailing Z)
which is not supported by older versions of fromisoformat.
"""
# Python 3.11+ supports Z suffix natively in fromisoformat
if sys.version_info >= (3, 11):
return datetime.datetime.fromisoformat(isodate)
else:
return datetime.datetime.fromisoformat(
isodate.replace("Z", "+00:00")
if isodate.endswith("Z") and ("T" in isodate or " " in isodate.strip())
else isodate
)

View File

@@ -0,0 +1,126 @@
"""
A module that implements tooling to enable easy warnings about deprecations.
"""
from __future__ import annotations
import logging
import warnings
from typing import Any, TextIO
from pip._vendor.packaging.version import parse
from pip import __version__ as current_version # NOTE: tests patch this name.
DEPRECATION_MSG_PREFIX = "DEPRECATION: "
class PipDeprecationWarning(Warning):
pass
_original_showwarning: Any = None
# Warnings <-> Logging Integration
def _showwarning(
message: Warning | str,
category: type[Warning],
filename: str,
lineno: int,
file: TextIO | None = None,
line: str | None = None,
) -> None:
if file is not None:
if _original_showwarning is not None:
_original_showwarning(message, category, filename, lineno, file, line)
elif issubclass(category, PipDeprecationWarning):
# We use a specially named logger which will handle all of the
# deprecation messages for pip.
logger = logging.getLogger("pip._internal.deprecations")
logger.warning(message)
else:
_original_showwarning(message, category, filename, lineno, file, line)
def install_warning_logger() -> None:
# Enable our Deprecation Warnings
warnings.simplefilter("default", PipDeprecationWarning, append=True)
global _original_showwarning
if _original_showwarning is None:
_original_showwarning = warnings.showwarning
warnings.showwarning = _showwarning
def deprecated(
*,
reason: str,
replacement: str | None,
gone_in: str | None,
feature_flag: str | None = None,
issue: int | None = None,
) -> None:
"""Helper to deprecate existing functionality.
reason:
Textual reason shown to the user about why this functionality has
been deprecated. Should be a complete sentence.
replacement:
Textual suggestion shown to the user about what alternative
functionality they can use.
gone_in:
The version of pip does this functionality should get removed in.
Raises an error if pip's current version is greater than or equal to
this.
feature_flag:
Command-line flag of the form --use-feature={feature_flag} for testing
upcoming functionality.
issue:
Issue number on the tracker that would serve as a useful place for
users to find related discussion and provide feedback.
"""
# Determine whether or not the feature is already gone in this version.
is_gone = gone_in is not None and parse(current_version) >= parse(gone_in)
message_parts = [
(reason, f"{DEPRECATION_MSG_PREFIX}{{}}"),
(
gone_in,
(
"pip {} will enforce this behaviour change."
if not is_gone
else "Since pip {}, this is no longer supported."
),
),
(
replacement,
"A possible replacement is {}.",
),
(
feature_flag,
(
"You can use the flag --use-feature={} to test the upcoming behaviour."
if not is_gone
else None
),
),
(
issue,
"Discussion can be found at https://github.com/pypa/pip/issues/{}",
),
]
message = " ".join(
format_str.format(value)
for value, format_str in message_parts
if format_str is not None and value is not None
)
# Raise as an error if this behaviour is deprecated.
if is_gone:
raise PipDeprecationWarning(message)
warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)

View File

@@ -0,0 +1,87 @@
from __future__ import annotations
from pip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo
from pip._internal.models.link import Link
from pip._internal.utils.urls import path_to_url
from pip._internal.vcs import vcs
def direct_url_as_pep440_direct_reference(direct_url: DirectUrl, name: str) -> str:
"""Convert a DirectUrl to a pip requirement string."""
direct_url.validate() # if invalid, this is a pip bug
requirement = name + " @ "
fragments = []
if isinstance(direct_url.info, VcsInfo):
requirement += (
f"{direct_url.info.vcs}+{direct_url.url}@{direct_url.info.commit_id}"
)
elif isinstance(direct_url.info, ArchiveInfo):
requirement += direct_url.url
if direct_url.info.hash:
fragments.append(direct_url.info.hash)
else:
assert isinstance(direct_url.info, DirInfo)
requirement += direct_url.url
if direct_url.subdirectory:
fragments.append("subdirectory=" + direct_url.subdirectory)
if fragments:
requirement += "#" + "&".join(fragments)
return requirement
def direct_url_for_editable(source_dir: str) -> DirectUrl:
return DirectUrl(
url=path_to_url(source_dir),
info=DirInfo(editable=True),
)
def direct_url_from_link(
link: Link, source_dir: str | None = None, link_is_in_wheel_cache: bool = False
) -> DirectUrl:
if link.is_vcs:
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
assert vcs_backend
url, requested_revision, _ = vcs_backend.get_url_rev_and_auth(
link.url_without_fragment
)
# For VCS links, we need to find out and add commit_id.
if link_is_in_wheel_cache:
# If the requested VCS link corresponds to a cached
# wheel, it means the requested revision was an
# immutable commit hash, otherwise it would not have
# been cached. In that case we don't have a source_dir
# with the VCS checkout.
assert requested_revision
commit_id = requested_revision
else:
# If the wheel was not in cache, it means we have
# had to checkout from VCS to build and we have a source_dir
# which we can inspect to find out the commit id.
assert source_dir
commit_id = vcs_backend.get_revision(source_dir)
return DirectUrl(
url=url,
info=VcsInfo(
vcs=vcs_backend.name,
commit_id=commit_id,
requested_revision=requested_revision,
),
subdirectory=link.subdirectory_fragment,
)
elif link.is_existing_dir():
return DirectUrl(
url=link.url_without_fragment,
info=DirInfo(),
subdirectory=link.subdirectory_fragment,
)
else:
hash = None
hash_name = link.hash_name
if hash_name:
hash = f"{hash_name}={link.hash}"
return DirectUrl(
url=link.url_without_fragment,
info=ArchiveInfo(hash=hash),
subdirectory=link.subdirectory_fragment,
)

View File

@@ -0,0 +1,81 @@
from __future__ import annotations
import os
import re
import sys
from pip._internal.locations import site_packages, user_site
from pip._internal.utils.virtualenv import (
running_under_virtualenv,
virtualenv_no_global,
)
__all__ = [
"egg_link_path_from_sys_path",
"egg_link_path_from_location",
]
def _egg_link_names(raw_name: str) -> list[str]:
"""
Convert a Name metadata value to a .egg-link name, by applying
the same substitution as pkg_resources's safe_name function.
Note: we cannot use canonicalize_name because it has a different logic.
We also look for the raw name (without normalization) as setuptools 69 changed
the way it names .egg-link files (https://github.com/pypa/setuptools/issues/4167).
"""
return [
re.sub("[^A-Za-z0-9.]+", "-", raw_name) + ".egg-link",
f"{raw_name}.egg-link",
]
def egg_link_path_from_sys_path(raw_name: str) -> str | None:
"""
Look for a .egg-link file for project name, by walking sys.path.
"""
egg_link_names = _egg_link_names(raw_name)
for path_item in sys.path:
for egg_link_name in egg_link_names:
egg_link = os.path.join(path_item, egg_link_name)
if os.path.isfile(egg_link):
return egg_link
return None
def egg_link_path_from_location(raw_name: str) -> str | None:
"""
Return the path for the .egg-link file if it exists, otherwise, None.
There's 3 scenarios:
1) not in a virtualenv
try to find in site.USER_SITE, then site_packages
2) in a no-global virtualenv
try to find in site_packages
3) in a yes-global virtualenv
try to find in site_packages, then site.USER_SITE
(don't look in global location)
For #1 and #3, there could be odd cases, where there's an egg-link in 2
locations.
This method will just return the first one found.
"""
sites: list[str] = []
if running_under_virtualenv():
sites.append(site_packages)
if not virtualenv_no_global() and user_site:
sites.append(user_site)
else:
if user_site:
sites.append(user_site)
sites.append(site_packages)
egg_link_names = _egg_link_names(raw_name)
for site in sites:
for egg_link_name in egg_link_names:
egglink = os.path.join(site, egg_link_name)
if os.path.isfile(egglink):
return egglink
return None