Загрузить файлы в «venv/Lib/site-packages/pip/_vendor/packaking»
This commit is contained in:
1068
venv/Lib/site-packages/pip/_vendor/packaking/specifiers.py
Normal file
1068
venv/Lib/site-packages/pip/_vendor/packaking/specifiers.py
Normal file
File diff suppressed because it is too large
Load Diff
651
venv/Lib/site-packages/pip/_vendor/packaking/tags.py
Normal file
651
venv/Lib/site-packages/pip/_vendor/packaking/tags.py
Normal file
@@ -0,0 +1,651 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from typing import (
|
||||
Any,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Sequence,
|
||||
Tuple,
|
||||
cast,
|
||||
)
|
||||
|
||||
from . import _manylinux, _musllinux
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PythonVersion = Sequence[int]
|
||||
AppleVersion = Tuple[int, int]
|
||||
|
||||
INTERPRETER_SHORT_NAMES: dict[str, str] = {
|
||||
"python": "py", # Generic.
|
||||
"cpython": "cp",
|
||||
"pypy": "pp",
|
||||
"ironpython": "ip",
|
||||
"jython": "jy",
|
||||
}
|
||||
|
||||
|
||||
_32_BIT_INTERPRETER = struct.calcsize("P") == 4
|
||||
|
||||
|
||||
class Tag:
|
||||
"""
|
||||
A representation of the tag triple for a wheel.
|
||||
|
||||
Instances are considered immutable and thus are hashable. Equality checking
|
||||
is also supported.
|
||||
"""
|
||||
|
||||
__slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
|
||||
|
||||
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
|
||||
self._interpreter = interpreter.lower()
|
||||
self._abi = abi.lower()
|
||||
self._platform = platform.lower()
|
||||
# The __hash__ of every single element in a Set[Tag] will be evaluated each time
|
||||
# that a set calls its `.disjoint()` method, which may be called hundreds of
|
||||
# times when scanning a page of links for packages with tags matching that
|
||||
# Set[Tag]. Pre-computing the value here produces significant speedups for
|
||||
# downstream consumers.
|
||||
self._hash = hash((self._interpreter, self._abi, self._platform))
|
||||
|
||||
@property
|
||||
def interpreter(self) -> str:
|
||||
return self._interpreter
|
||||
|
||||
@property
|
||||
def abi(self) -> str:
|
||||
return self._abi
|
||||
|
||||
@property
|
||||
def platform(self) -> str:
|
||||
return self._platform
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Tag):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
(self._hash == other._hash) # Short-circuit ASAP for perf reasons.
|
||||
and (self._platform == other._platform)
|
||||
and (self._abi == other._abi)
|
||||
and (self._interpreter == other._interpreter)
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return self._hash
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self._interpreter}-{self._abi}-{self._platform}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self} @ {id(self)}>"
|
||||
|
||||
def __setstate__(self, state: tuple[None, dict[str, Any]]) -> None:
|
||||
# The cached _hash is wrong when unpickling.
|
||||
_, slots = state
|
||||
for k, v in slots.items():
|
||||
setattr(self, k, v)
|
||||
self._hash = hash((self._interpreter, self._abi, self._platform))
|
||||
|
||||
|
||||
def parse_tag(tag: str) -> frozenset[Tag]:
|
||||
"""
|
||||
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
|
||||
|
||||
Returning a set is required due to the possibility that the tag is a
|
||||
compressed tag set.
|
||||
"""
|
||||
tags = set()
|
||||
interpreters, abis, platforms = tag.split("-")
|
||||
for interpreter in interpreters.split("."):
|
||||
for abi in abis.split("."):
|
||||
for platform_ in platforms.split("."):
|
||||
tags.add(Tag(interpreter, abi, platform_))
|
||||
return frozenset(tags)
|
||||
|
||||
|
||||
def _get_config_var(name: str, warn: bool = False) -> int | str | None:
|
||||
value: int | str | None = sysconfig.get_config_var(name)
|
||||
if value is None and warn:
|
||||
logger.debug(
|
||||
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def _normalize_string(string: str) -> str:
|
||||
return string.replace(".", "_").replace("-", "_").replace(" ", "_")
|
||||
|
||||
|
||||
def _is_threaded_cpython(abis: list[str]) -> bool:
|
||||
"""
|
||||
Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
|
||||
|
||||
The threaded builds are indicated by a "t" in the abiflags.
|
||||
"""
|
||||
if len(abis) == 0:
|
||||
return False
|
||||
# expect e.g., cp313
|
||||
m = re.match(r"cp\d+(.*)", abis[0])
|
||||
if not m:
|
||||
return False
|
||||
abiflags = m.group(1)
|
||||
return "t" in abiflags
|
||||
|
||||
|
||||
def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
|
||||
"""
|
||||
Determine if the Python version supports abi3.
|
||||
|
||||
PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
|
||||
builds do not support abi3.
|
||||
"""
|
||||
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
|
||||
|
||||
|
||||
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
|
||||
py_version = tuple(py_version) # To allow for version comparison.
|
||||
abis = []
|
||||
version = _version_nodot(py_version[:2])
|
||||
threading = debug = pymalloc = ucs4 = ""
|
||||
with_debug = _get_config_var("Py_DEBUG", warn)
|
||||
has_refcount = hasattr(sys, "gettotalrefcount")
|
||||
# Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
|
||||
# extension modules is the best option.
|
||||
# https://github.com/pypa/pip/issues/3383#issuecomment-173267692
|
||||
has_ext = "_d.pyd" in EXTENSION_SUFFIXES
|
||||
if with_debug or (with_debug is None and (has_refcount or has_ext)):
|
||||
debug = "d"
|
||||
if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
|
||||
threading = "t"
|
||||
if py_version < (3, 8):
|
||||
with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
|
||||
if with_pymalloc or with_pymalloc is None:
|
||||
pymalloc = "m"
|
||||
if py_version < (3, 3):
|
||||
unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
|
||||
if unicode_size == 4 or (
|
||||
unicode_size is None and sys.maxunicode == 0x10FFFF
|
||||
):
|
||||
ucs4 = "u"
|
||||
elif debug:
|
||||
# Debug builds can also load "normal" extension modules.
|
||||
# We can also assume no UCS-4 or pymalloc requirement.
|
||||
abis.append(f"cp{version}{threading}")
|
||||
abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
|
||||
return abis
|
||||
|
||||
|
||||
def cpython_tags(
|
||||
python_version: PythonVersion | None = None,
|
||||
abis: Iterable[str] | None = None,
|
||||
platforms: Iterable[str] | None = None,
|
||||
*,
|
||||
warn: bool = False,
|
||||
) -> Iterator[Tag]:
|
||||
"""
|
||||
Yields the tags for a CPython interpreter.
|
||||
|
||||
The tags consist of:
|
||||
- cp<python_version>-<abi>-<platform>
|
||||
- cp<python_version>-abi3-<platform>
|
||||
- cp<python_version>-none-<platform>
|
||||
- cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2.
|
||||
|
||||
If python_version only specifies a major version then user-provided ABIs and
|
||||
the 'none' ABItag will be used.
|
||||
|
||||
If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
|
||||
their normal position and not at the beginning.
|
||||
"""
|
||||
if not python_version:
|
||||
python_version = sys.version_info[:2]
|
||||
|
||||
interpreter = f"cp{_version_nodot(python_version[:2])}"
|
||||
|
||||
if abis is None:
|
||||
abis = _cpython_abis(python_version, warn) if len(python_version) > 1 else []
|
||||
abis = list(abis)
|
||||
# 'abi3' and 'none' are explicitly handled later.
|
||||
for explicit_abi in ("abi3", "none"):
|
||||
try:
|
||||
abis.remove(explicit_abi)
|
||||
except ValueError: # noqa: PERF203
|
||||
pass
|
||||
|
||||
platforms = list(platforms or platform_tags())
|
||||
for abi in abis:
|
||||
for platform_ in platforms:
|
||||
yield Tag(interpreter, abi, platform_)
|
||||
|
||||
threading = _is_threaded_cpython(abis)
|
||||
use_abi3 = _abi3_applies(python_version, threading)
|
||||
if use_abi3:
|
||||
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
|
||||
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
|
||||
|
||||
if use_abi3:
|
||||
for minor_version in range(python_version[1] - 1, 1, -1):
|
||||
for platform_ in platforms:
|
||||
version = _version_nodot((python_version[0], minor_version))
|
||||
interpreter = f"cp{version}"
|
||||
yield Tag(interpreter, "abi3", platform_)
|
||||
|
||||
|
||||
def _generic_abi() -> list[str]:
|
||||
"""
|
||||
Return the ABI tag based on EXT_SUFFIX.
|
||||
"""
|
||||
# The following are examples of `EXT_SUFFIX`.
|
||||
# We want to keep the parts which are related to the ABI and remove the
|
||||
# parts which are related to the platform:
|
||||
# - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
|
||||
# - mac: '.cpython-310-darwin.so' => cp310
|
||||
# - win: '.cp310-win_amd64.pyd' => cp310
|
||||
# - win: '.pyd' => cp37 (uses _cpython_abis())
|
||||
# - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
|
||||
# - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
|
||||
# => graalpy_38_native
|
||||
|
||||
ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
|
||||
if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
|
||||
raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
|
||||
parts = ext_suffix.split(".")
|
||||
if len(parts) < 3:
|
||||
# CPython3.7 and earlier uses ".pyd" on Windows.
|
||||
return _cpython_abis(sys.version_info[:2])
|
||||
soabi = parts[1]
|
||||
if soabi.startswith("cpython"):
|
||||
# non-windows
|
||||
abi = "cp" + soabi.split("-")[1]
|
||||
elif soabi.startswith("cp"):
|
||||
# windows
|
||||
abi = soabi.split("-")[0]
|
||||
elif soabi.startswith("pypy"):
|
||||
abi = "-".join(soabi.split("-")[:2])
|
||||
elif soabi.startswith("graalpy"):
|
||||
abi = "-".join(soabi.split("-")[:3])
|
||||
elif soabi:
|
||||
# pyston, ironpython, others?
|
||||
abi = soabi
|
||||
else:
|
||||
return []
|
||||
return [_normalize_string(abi)]
|
||||
|
||||
|
||||
def generic_tags(
|
||||
interpreter: str | None = None,
|
||||
abis: Iterable[str] | None = None,
|
||||
platforms: Iterable[str] | None = None,
|
||||
*,
|
||||
warn: bool = False,
|
||||
) -> Iterator[Tag]:
|
||||
"""
|
||||
Yields the tags for a generic interpreter.
|
||||
|
||||
The tags consist of:
|
||||
- <interpreter>-<abi>-<platform>
|
||||
|
||||
The "none" ABI will be added if it was not explicitly provided.
|
||||
"""
|
||||
if not interpreter:
|
||||
interp_name = interpreter_name()
|
||||
interp_version = interpreter_version(warn=warn)
|
||||
interpreter = f"{interp_name}{interp_version}"
|
||||
abis = _generic_abi() if abis is None else list(abis)
|
||||
platforms = list(platforms or platform_tags())
|
||||
if "none" not in abis:
|
||||
abis.append("none")
|
||||
for abi in abis:
|
||||
for platform_ in platforms:
|
||||
yield Tag(interpreter, abi, platform_)
|
||||
|
||||
|
||||
def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
|
||||
"""
|
||||
Yields Python versions in descending order.
|
||||
|
||||
After the latest version, the major-only version will be yielded, and then
|
||||
all previous versions of that major version.
|
||||
"""
|
||||
if len(py_version) > 1:
|
||||
yield f"py{_version_nodot(py_version[:2])}"
|
||||
yield f"py{py_version[0]}"
|
||||
if len(py_version) > 1:
|
||||
for minor in range(py_version[1] - 1, -1, -1):
|
||||
yield f"py{_version_nodot((py_version[0], minor))}"
|
||||
|
||||
|
||||
def compatible_tags(
|
||||
python_version: PythonVersion | None = None,
|
||||
interpreter: str | None = None,
|
||||
platforms: Iterable[str] | None = None,
|
||||
) -> Iterator[Tag]:
|
||||
"""
|
||||
Yields the sequence of tags that are compatible with a specific version of Python.
|
||||
|
||||
The tags consist of:
|
||||
- py*-none-<platform>
|
||||
- <interpreter>-none-any # ... if `interpreter` is provided.
|
||||
- py*-none-any
|
||||
"""
|
||||
if not python_version:
|
||||
python_version = sys.version_info[:2]
|
||||
platforms = list(platforms or platform_tags())
|
||||
for version in _py_interpreter_range(python_version):
|
||||
for platform_ in platforms:
|
||||
yield Tag(version, "none", platform_)
|
||||
if interpreter:
|
||||
yield Tag(interpreter, "none", "any")
|
||||
for version in _py_interpreter_range(python_version):
|
||||
yield Tag(version, "none", "any")
|
||||
|
||||
|
||||
def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
|
||||
if not is_32bit:
|
||||
return arch
|
||||
|
||||
if arch.startswith("ppc"):
|
||||
return "ppc"
|
||||
|
||||
return "i386"
|
||||
|
||||
|
||||
def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
|
||||
formats = [cpu_arch]
|
||||
if cpu_arch == "x86_64":
|
||||
if version < (10, 4):
|
||||
return []
|
||||
formats.extend(["intel", "fat64", "fat32"])
|
||||
|
||||
elif cpu_arch == "i386":
|
||||
if version < (10, 4):
|
||||
return []
|
||||
formats.extend(["intel", "fat32", "fat"])
|
||||
|
||||
elif cpu_arch == "ppc64":
|
||||
# TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
|
||||
if version > (10, 5) or version < (10, 4):
|
||||
return []
|
||||
formats.append("fat64")
|
||||
|
||||
elif cpu_arch == "ppc":
|
||||
if version > (10, 6):
|
||||
return []
|
||||
formats.extend(["fat32", "fat"])
|
||||
|
||||
if cpu_arch in {"arm64", "x86_64"}:
|
||||
formats.append("universal2")
|
||||
|
||||
if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
|
||||
formats.append("universal")
|
||||
|
||||
return formats
|
||||
|
||||
|
||||
def mac_platforms(
|
||||
version: AppleVersion | None = None, arch: str | None = None
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Yields the platform tags for a macOS system.
|
||||
|
||||
The `version` parameter is a two-item tuple specifying the macOS version to
|
||||
generate platform tags for. The `arch` parameter is the CPU architecture to
|
||||
generate platform tags for. Both parameters default to the appropriate value
|
||||
for the current system.
|
||||
"""
|
||||
version_str, _, cpu_arch = platform.mac_ver()
|
||||
if version is None:
|
||||
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
||||
if version == (10, 16):
|
||||
# When built against an older macOS SDK, Python will report macOS 10.16
|
||||
# instead of the real version.
|
||||
version_str = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-sS",
|
||||
"-c",
|
||||
"import platform; print(platform.mac_ver()[0])",
|
||||
],
|
||||
check=True,
|
||||
env={"SYSTEM_VERSION_COMPAT": "0"},
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
).stdout
|
||||
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
||||
|
||||
if arch is None:
|
||||
arch = _mac_arch(cpu_arch)
|
||||
|
||||
if (10, 0) <= version < (11, 0):
|
||||
# Prior to Mac OS 11, each yearly release of Mac OS bumped the
|
||||
# "minor" version number. The major version was always 10.
|
||||
major_version = 10
|
||||
for minor_version in range(version[1], -1, -1):
|
||||
compat_version = major_version, minor_version
|
||||
binary_formats = _mac_binary_formats(compat_version, arch)
|
||||
for binary_format in binary_formats:
|
||||
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
||||
|
||||
if version >= (11, 0):
|
||||
# Starting with Mac OS 11, each yearly release bumps the major version
|
||||
# number. The minor versions are now the midyear updates.
|
||||
minor_version = 0
|
||||
for major_version in range(version[0], 10, -1):
|
||||
compat_version = major_version, minor_version
|
||||
binary_formats = _mac_binary_formats(compat_version, arch)
|
||||
for binary_format in binary_formats:
|
||||
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
||||
|
||||
if version >= (11, 0):
|
||||
# Mac OS 11 on x86_64 is compatible with binaries from previous releases.
|
||||
# Arm64 support was introduced in 11.0, so no Arm binaries from previous
|
||||
# releases exist.
|
||||
#
|
||||
# However, the "universal2" binary format can have a
|
||||
# macOS version earlier than 11.0 when the x86_64 part of the binary supports
|
||||
# that version of macOS.
|
||||
major_version = 10
|
||||
if arch == "x86_64":
|
||||
for minor_version in range(16, 3, -1):
|
||||
compat_version = major_version, minor_version
|
||||
binary_formats = _mac_binary_formats(compat_version, arch)
|
||||
for binary_format in binary_formats:
|
||||
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
||||
else:
|
||||
for minor_version in range(16, 3, -1):
|
||||
compat_version = major_version, minor_version
|
||||
binary_format = "universal2"
|
||||
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
||||
|
||||
|
||||
def ios_platforms(
|
||||
version: AppleVersion | None = None, multiarch: str | None = None
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Yields the platform tags for an iOS system.
|
||||
|
||||
:param version: A two-item tuple specifying the iOS version to generate
|
||||
platform tags for. Defaults to the current iOS version.
|
||||
:param multiarch: The CPU architecture+ABI to generate platform tags for -
|
||||
(the value used by `sys.implementation._multiarch` e.g.,
|
||||
`arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
|
||||
multiarch value.
|
||||
"""
|
||||
if version is None:
|
||||
# if iOS is the current platform, ios_ver *must* be defined. However,
|
||||
# it won't exist for CPython versions before 3.13, which causes a mypy
|
||||
# error.
|
||||
_, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore]
|
||||
version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
|
||||
|
||||
if multiarch is None:
|
||||
multiarch = sys.implementation._multiarch
|
||||
multiarch = multiarch.replace("-", "_")
|
||||
|
||||
ios_platform_template = "ios_{major}_{minor}_{multiarch}"
|
||||
|
||||
# Consider any iOS major.minor version from the version requested, down to
|
||||
# 12.0. 12.0 is the first iOS version that is known to have enough features
|
||||
# to support CPython. Consider every possible minor release up to X.9. There
|
||||
# highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
|
||||
# candidates that won't ever match doesn't really hurt, and it saves us from
|
||||
# having to keep an explicit list of known iOS versions in the code. Return
|
||||
# the results descending order of version number.
|
||||
|
||||
# If the requested major version is less than 12, there won't be any matches.
|
||||
if version[0] < 12:
|
||||
return
|
||||
|
||||
# Consider the actual X.Y version that was requested.
|
||||
yield ios_platform_template.format(
|
||||
major=version[0], minor=version[1], multiarch=multiarch
|
||||
)
|
||||
|
||||
# Consider every minor version from X.0 to the minor version prior to the
|
||||
# version requested by the platform.
|
||||
for minor in range(version[1] - 1, -1, -1):
|
||||
yield ios_platform_template.format(
|
||||
major=version[0], minor=minor, multiarch=multiarch
|
||||
)
|
||||
|
||||
for major in range(version[0] - 1, 11, -1):
|
||||
for minor in range(9, -1, -1):
|
||||
yield ios_platform_template.format(
|
||||
major=major, minor=minor, multiarch=multiarch
|
||||
)
|
||||
|
||||
|
||||
def android_platforms(
|
||||
api_level: int | None = None, abi: str | None = None
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Yields the :attr:`~Tag.platform` tags for Android. If this function is invoked on
|
||||
non-Android platforms, the ``api_level`` and ``abi`` arguments are required.
|
||||
|
||||
:param int api_level: The maximum `API level
|
||||
<https://developer.android.com/tools/releases/platforms>`__ to return. Defaults
|
||||
to the current system's version, as returned by ``platform.android_ver``.
|
||||
:param str abi: The `Android ABI <https://developer.android.com/ndk/guides/abis>`__,
|
||||
e.g. ``arm64_v8a``. Defaults to the current system's ABI , as returned by
|
||||
``sysconfig.get_platform``. Hyphens and periods will be replaced with
|
||||
underscores.
|
||||
"""
|
||||
if platform.system() != "Android" and (api_level is None or abi is None):
|
||||
raise TypeError(
|
||||
"on non-Android platforms, the api_level and abi arguments are required"
|
||||
)
|
||||
|
||||
if api_level is None:
|
||||
# Python 3.13 was the first version to return platform.system() == "Android",
|
||||
# and also the first version to define platform.android_ver().
|
||||
api_level = platform.android_ver().api_level # type: ignore[attr-defined]
|
||||
|
||||
if abi is None:
|
||||
abi = sysconfig.get_platform().split("-")[-1]
|
||||
abi = _normalize_string(abi)
|
||||
|
||||
# 16 is the minimum API level known to have enough features to support CPython
|
||||
# without major patching. Yield every API level from the maximum down to the
|
||||
# minimum, inclusive.
|
||||
min_api_level = 16
|
||||
for ver in range(api_level, min_api_level - 1, -1):
|
||||
yield f"android_{ver}_{abi}"
|
||||
|
||||
|
||||
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
|
||||
linux = _normalize_string(sysconfig.get_platform())
|
||||
if not linux.startswith("linux_"):
|
||||
# we should never be here, just yield the sysconfig one and return
|
||||
yield linux
|
||||
return
|
||||
if is_32bit:
|
||||
if linux == "linux_x86_64":
|
||||
linux = "linux_i686"
|
||||
elif linux == "linux_aarch64":
|
||||
linux = "linux_armv8l"
|
||||
_, arch = linux.split("_", 1)
|
||||
archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
|
||||
yield from _manylinux.platform_tags(archs)
|
||||
yield from _musllinux.platform_tags(archs)
|
||||
for arch in archs:
|
||||
yield f"linux_{arch}"
|
||||
|
||||
|
||||
def _generic_platforms() -> Iterator[str]:
|
||||
yield _normalize_string(sysconfig.get_platform())
|
||||
|
||||
|
||||
def platform_tags() -> Iterator[str]:
|
||||
"""
|
||||
Provides the platform tags for this installation.
|
||||
"""
|
||||
if platform.system() == "Darwin":
|
||||
return mac_platforms()
|
||||
elif platform.system() == "iOS":
|
||||
return ios_platforms()
|
||||
elif platform.system() == "Android":
|
||||
return android_platforms()
|
||||
elif platform.system() == "Linux":
|
||||
return _linux_platforms()
|
||||
else:
|
||||
return _generic_platforms()
|
||||
|
||||
|
||||
def interpreter_name() -> str:
|
||||
"""
|
||||
Returns the name of the running interpreter.
|
||||
|
||||
Some implementations have a reserved, two-letter abbreviation which will
|
||||
be returned when appropriate.
|
||||
"""
|
||||
name = sys.implementation.name
|
||||
return INTERPRETER_SHORT_NAMES.get(name) or name
|
||||
|
||||
|
||||
def interpreter_version(*, warn: bool = False) -> str:
|
||||
"""
|
||||
Returns the version of the running interpreter.
|
||||
"""
|
||||
version = _get_config_var("py_version_nodot", warn=warn)
|
||||
return str(version) if version else _version_nodot(sys.version_info[:2])
|
||||
|
||||
|
||||
def _version_nodot(version: PythonVersion) -> str:
|
||||
return "".join(map(str, version))
|
||||
|
||||
|
||||
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
||||
"""
|
||||
Returns the sequence of tag triples for the running interpreter.
|
||||
|
||||
The order of the sequence corresponds to priority order for the
|
||||
interpreter, from most to least important.
|
||||
"""
|
||||
|
||||
interp_name = interpreter_name()
|
||||
if interp_name == "cp":
|
||||
yield from cpython_tags(warn=warn)
|
||||
else:
|
||||
yield from generic_tags()
|
||||
|
||||
if interp_name == "pp":
|
||||
interp = "pp3"
|
||||
elif interp_name == "cp":
|
||||
interp = "cp" + interpreter_version(warn=warn)
|
||||
else:
|
||||
interp = None
|
||||
yield from compatible_tags(interpreter=interp)
|
||||
158
venv/Lib/site-packages/pip/_vendor/packaking/utils.py
Normal file
158
venv/Lib/site-packages/pip/_vendor/packaking/utils.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import NewType, Tuple, Union, cast
|
||||
|
||||
from .tags import Tag, parse_tag
|
||||
from .version import InvalidVersion, Version, _TrimmedRelease
|
||||
|
||||
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
||||
NormalizedName = NewType("NormalizedName", str)
|
||||
|
||||
|
||||
class InvalidName(ValueError):
|
||||
"""
|
||||
An invalid distribution name; users should refer to the packaging user guide.
|
||||
"""
|
||||
|
||||
|
||||
class InvalidWheelFilename(ValueError):
|
||||
"""
|
||||
An invalid wheel filename was found, users should refer to PEP 427.
|
||||
"""
|
||||
|
||||
|
||||
class InvalidSdistFilename(ValueError):
|
||||
"""
|
||||
An invalid sdist filename was found, users should refer to the packaging user guide.
|
||||
"""
|
||||
|
||||
|
||||
# Core metadata spec for `Name`
|
||||
_validate_regex = re.compile(r"[A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9]", re.IGNORECASE)
|
||||
_normalized_regex = re.compile(r"[a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9]")
|
||||
# PEP 427: The build number must start with a digit.
|
||||
_build_tag_regex = re.compile(r"(\d+)(.*)")
|
||||
|
||||
|
||||
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
|
||||
if validate and not _validate_regex.fullmatch(name):
|
||||
raise InvalidName(f"name is invalid: {name!r}")
|
||||
# Ensure all ``.`` and ``_`` are ``-``
|
||||
# Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503
|
||||
# Much faster than re, and even faster than str.translate
|
||||
value = name.lower().replace("_", "-").replace(".", "-")
|
||||
# Condense repeats (faster than regex)
|
||||
while "--" in value:
|
||||
value = value.replace("--", "-")
|
||||
return cast("NormalizedName", value)
|
||||
|
||||
|
||||
def is_normalized_name(name: str) -> bool:
|
||||
return _normalized_regex.fullmatch(name) is not None
|
||||
|
||||
|
||||
def canonicalize_version(
|
||||
version: Version | str, *, strip_trailing_zero: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
Return a canonical form of a version as a string.
|
||||
|
||||
>>> canonicalize_version('1.0.1')
|
||||
'1.0.1'
|
||||
|
||||
Per PEP 625, versions may have multiple canonical forms, differing
|
||||
only by trailing zeros.
|
||||
|
||||
>>> canonicalize_version('1.0.0')
|
||||
'1'
|
||||
>>> canonicalize_version('1.0.0', strip_trailing_zero=False)
|
||||
'1.0.0'
|
||||
|
||||
Invalid versions are returned unaltered.
|
||||
|
||||
>>> canonicalize_version('foo bar baz')
|
||||
'foo bar baz'
|
||||
"""
|
||||
if isinstance(version, str):
|
||||
try:
|
||||
version = Version(version)
|
||||
except InvalidVersion:
|
||||
return str(version)
|
||||
return str(_TrimmedRelease(version) if strip_trailing_zero else version)
|
||||
|
||||
|
||||
def parse_wheel_filename(
|
||||
filename: str,
|
||||
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
|
||||
if not filename.endswith(".whl"):
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
|
||||
)
|
||||
|
||||
filename = filename[:-4]
|
||||
dashes = filename.count("-")
|
||||
if dashes not in (4, 5):
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid wheel filename (wrong number of parts): {filename!r}"
|
||||
)
|
||||
|
||||
parts = filename.split("-", dashes - 2)
|
||||
name_part = parts[0]
|
||||
# See PEP 427 for the rules on escaping the project name.
|
||||
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
|
||||
raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
|
||||
name = canonicalize_name(name_part)
|
||||
|
||||
try:
|
||||
version = Version(parts[1])
|
||||
except InvalidVersion as e:
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid wheel filename (invalid version): {filename!r}"
|
||||
) from e
|
||||
|
||||
if dashes == 5:
|
||||
build_part = parts[2]
|
||||
build_match = _build_tag_regex.match(build_part)
|
||||
if build_match is None:
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid build number: {build_part} in {filename!r}"
|
||||
)
|
||||
build = cast("BuildTag", (int(build_match.group(1)), build_match.group(2)))
|
||||
else:
|
||||
build = ()
|
||||
tags = parse_tag(parts[-1])
|
||||
return (name, version, build, tags)
|
||||
|
||||
|
||||
def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
|
||||
if filename.endswith(".tar.gz"):
|
||||
file_stem = filename[: -len(".tar.gz")]
|
||||
elif filename.endswith(".zip"):
|
||||
file_stem = filename[: -len(".zip")]
|
||||
else:
|
||||
raise InvalidSdistFilename(
|
||||
f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
|
||||
f" {filename!r}"
|
||||
)
|
||||
|
||||
# We are requiring a PEP 440 version, which cannot contain dashes,
|
||||
# so we split on the last dash.
|
||||
name_part, sep, version_part = file_stem.rpartition("-")
|
||||
if not sep:
|
||||
raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}")
|
||||
|
||||
name = canonicalize_name(name_part)
|
||||
|
||||
try:
|
||||
version = Version(version_part)
|
||||
except InvalidVersion as e:
|
||||
raise InvalidSdistFilename(
|
||||
f"Invalid sdist filename (invalid version): {filename!r}"
|
||||
) from e
|
||||
|
||||
return (name, version)
|
||||
792
venv/Lib/site-packages/pip/_vendor/packaking/version.py
Normal file
792
venv/Lib/site-packages/pip/_vendor/packaking/version.py
Normal file
@@ -0,0 +1,792 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
"""
|
||||
.. testsetup::
|
||||
|
||||
from pip._vendor.packaging.version import parse, Version
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
import typing
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
SupportsInt,
|
||||
Tuple,
|
||||
TypedDict,
|
||||
Union,
|
||||
)
|
||||
|
||||
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing_extensions import Self, Unpack
|
||||
|
||||
if sys.version_info >= (3, 13): # pragma: no cover
|
||||
from warnings import deprecated as _deprecated
|
||||
elif typing.TYPE_CHECKING:
|
||||
from typing_extensions import deprecated as _deprecated
|
||||
else: # pragma: no cover
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
def _deprecated(message: str) -> object:
|
||||
def decorator(func: object) -> object:
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args: object, **kwargs: object) -> object:
|
||||
warnings.warn(
|
||||
message,
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
_LETTER_NORMALIZATION = {
|
||||
"alpha": "a",
|
||||
"beta": "b",
|
||||
"c": "rc",
|
||||
"pre": "rc",
|
||||
"preview": "rc",
|
||||
"rev": "post",
|
||||
"r": "post",
|
||||
}
|
||||
|
||||
__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"]
|
||||
|
||||
LocalType = Tuple[Union[int, str], ...]
|
||||
|
||||
CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
|
||||
CmpLocalType = Union[
|
||||
NegativeInfinityType,
|
||||
Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
|
||||
]
|
||||
CmpKey = Tuple[
|
||||
int,
|
||||
Tuple[int, ...],
|
||||
CmpPrePostDevType,
|
||||
CmpPrePostDevType,
|
||||
CmpPrePostDevType,
|
||||
CmpLocalType,
|
||||
]
|
||||
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
|
||||
|
||||
|
||||
class _VersionReplace(TypedDict, total=False):
|
||||
epoch: int | None
|
||||
release: tuple[int, ...] | None
|
||||
pre: tuple[Literal["a", "b", "rc"], int] | None
|
||||
post: int | None
|
||||
dev: int | None
|
||||
local: str | None
|
||||
|
||||
|
||||
def parse(version: str) -> Version:
|
||||
"""Parse the given version string.
|
||||
|
||||
>>> parse('1.0.dev1')
|
||||
<Version('1.0.dev1')>
|
||||
|
||||
:param version: The version string to parse.
|
||||
:raises InvalidVersion: When the version string is not a valid version.
|
||||
"""
|
||||
return Version(version)
|
||||
|
||||
|
||||
class InvalidVersion(ValueError):
|
||||
"""Raised when a version string is not a valid version.
|
||||
|
||||
>>> Version("invalid")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
packaging.version.InvalidVersion: Invalid version: 'invalid'
|
||||
"""
|
||||
|
||||
|
||||
class _BaseVersion:
|
||||
__slots__ = ()
|
||||
|
||||
# This can also be a normal member (see the packaging_legacy package);
|
||||
# we are just requiring it to be readable. Actually defining a property
|
||||
# has runtime effect on subclasses, so it's typing only.
|
||||
if typing.TYPE_CHECKING:
|
||||
|
||||
@property
|
||||
def _key(self) -> tuple[Any, ...]: ...
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._key)
|
||||
|
||||
# Please keep the duplicated `isinstance` check
|
||||
# in the six comparisons hereunder
|
||||
# unless you find a way to avoid adding overhead function calls.
|
||||
def __lt__(self, other: _BaseVersion) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key < other._key
|
||||
|
||||
def __le__(self, other: _BaseVersion) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key <= other._key
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key == other._key
|
||||
|
||||
def __ge__(self, other: _BaseVersion) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key >= other._key
|
||||
|
||||
def __gt__(self, other: _BaseVersion) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key > other._key
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key != other._key
|
||||
|
||||
|
||||
# Deliberately not anchored to the start and end of the string, to make it
|
||||
# easier for 3rd party code to reuse
|
||||
|
||||
# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
|
||||
_VERSION_PATTERN = r"""
|
||||
v?+ # optional leading v
|
||||
(?:
|
||||
(?:(?P<epoch>[0-9]+)!)?+ # epoch
|
||||
(?P<release>[0-9]+(?:\.[0-9]+)*+) # release segment
|
||||
(?P<pre> # pre-release
|
||||
[._-]?+
|
||||
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
||||
[._-]?+
|
||||
(?P<pre_n>[0-9]+)?
|
||||
)?+
|
||||
(?P<post> # post release
|
||||
(?:-(?P<post_n1>[0-9]+))
|
||||
|
|
||||
(?:
|
||||
[._-]?
|
||||
(?P<post_l>post|rev|r)
|
||||
[._-]?
|
||||
(?P<post_n2>[0-9]+)?
|
||||
)
|
||||
)?+
|
||||
(?P<dev> # dev release
|
||||
[._-]?+
|
||||
(?P<dev_l>dev)
|
||||
[._-]?+
|
||||
(?P<dev_n>[0-9]+)?
|
||||
)?+
|
||||
)
|
||||
(?:\+
|
||||
(?P<local> # local version
|
||||
[a-z0-9]+
|
||||
(?:[._-][a-z0-9]+)*+
|
||||
)
|
||||
)?+
|
||||
"""
|
||||
|
||||
_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
|
||||
|
||||
# Possessive qualifiers were added in Python 3.11.
|
||||
# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
|
||||
# Older PyPy also had a bug.
|
||||
VERSION_PATTERN = (
|
||||
_VERSION_PATTERN_OLD
|
||||
if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
|
||||
or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
|
||||
or sys.version_info < (3, 11)
|
||||
else _VERSION_PATTERN
|
||||
)
|
||||
"""
|
||||
A string containing the regular expression used to match a valid version.
|
||||
|
||||
The pattern is not anchored at either end, and is intended for embedding in larger
|
||||
expressions (for example, matching a version number as part of a file name). The
|
||||
regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
|
||||
flags set.
|
||||
|
||||
:meta hide-value:
|
||||
"""
|
||||
|
||||
|
||||
# Validation pattern for local version in replace()
|
||||
_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE)
|
||||
|
||||
|
||||
def _validate_epoch(value: object, /) -> int:
|
||||
epoch = value or 0
|
||||
if isinstance(epoch, int) and epoch >= 0:
|
||||
return epoch
|
||||
msg = f"epoch must be non-negative integer, got {epoch}"
|
||||
raise InvalidVersion(msg)
|
||||
|
||||
|
||||
def _validate_release(value: object, /) -> tuple[int, ...]:
|
||||
release = (0,) if value is None else value
|
||||
if (
|
||||
isinstance(release, tuple)
|
||||
and len(release) > 0
|
||||
and all(isinstance(i, int) and i >= 0 for i in release)
|
||||
):
|
||||
return release
|
||||
msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
|
||||
raise InvalidVersion(msg)
|
||||
|
||||
|
||||
def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
|
||||
if value is None:
|
||||
return value
|
||||
if (
|
||||
isinstance(value, tuple)
|
||||
and len(value) == 2
|
||||
and value[0] in ("a", "b", "rc")
|
||||
and isinstance(value[1], int)
|
||||
and value[1] >= 0
|
||||
):
|
||||
return value
|
||||
msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
|
||||
raise InvalidVersion(msg)
|
||||
|
||||
|
||||
def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
|
||||
if value is None:
|
||||
return value
|
||||
if isinstance(value, int) and value >= 0:
|
||||
return ("post", value)
|
||||
msg = f"post must be non-negative integer, got {value}"
|
||||
raise InvalidVersion(msg)
|
||||
|
||||
|
||||
def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
|
||||
if value is None:
|
||||
return value
|
||||
if isinstance(value, int) and value >= 0:
|
||||
return ("dev", value)
|
||||
msg = f"dev must be non-negative integer, got {value}"
|
||||
raise InvalidVersion(msg)
|
||||
|
||||
|
||||
def _validate_local(value: object, /) -> LocalType | None:
|
||||
if value is None:
|
||||
return value
|
||||
if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
|
||||
return _parse_local_version(value)
|
||||
msg = f"local must be a valid version string, got {value!r}"
|
||||
raise InvalidVersion(msg)
|
||||
|
||||
|
||||
# Backward compatibility for internals before 26.0. Do not use.
|
||||
class _Version(NamedTuple):
|
||||
epoch: int
|
||||
release: tuple[int, ...]
|
||||
dev: tuple[str, int] | None
|
||||
pre: tuple[str, int] | None
|
||||
post: tuple[str, int] | None
|
||||
local: LocalType | None
|
||||
|
||||
|
||||
class Version(_BaseVersion):
|
||||
"""This class abstracts handling of a project's versions.
|
||||
|
||||
A :class:`Version` instance is comparison aware and can be compared and
|
||||
sorted using the standard Python interfaces.
|
||||
|
||||
>>> v1 = Version("1.0a5")
|
||||
>>> v2 = Version("1.0")
|
||||
>>> v1
|
||||
<Version('1.0a5')>
|
||||
>>> v2
|
||||
<Version('1.0')>
|
||||
>>> v1 < v2
|
||||
True
|
||||
>>> v1 == v2
|
||||
False
|
||||
>>> v1 > v2
|
||||
False
|
||||
>>> v1 >= v2
|
||||
False
|
||||
>>> v1 <= v2
|
||||
True
|
||||
"""
|
||||
|
||||
__slots__ = ("_dev", "_epoch", "_key_cache", "_local", "_post", "_pre", "_release")
|
||||
__match_args__ = ("_str",)
|
||||
|
||||
_regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
_epoch: int
|
||||
_release: tuple[int, ...]
|
||||
_dev: tuple[str, int] | None
|
||||
_pre: tuple[str, int] | None
|
||||
_post: tuple[str, int] | None
|
||||
_local: LocalType | None
|
||||
|
||||
_key_cache: CmpKey | None
|
||||
|
||||
def __init__(self, version: str) -> None:
|
||||
"""Initialize a Version object.
|
||||
|
||||
:param version:
|
||||
The string representation of a version which will be parsed and normalized
|
||||
before use.
|
||||
:raises InvalidVersion:
|
||||
If the ``version`` does not conform to PEP 440 in any way then this
|
||||
exception will be raised.
|
||||
"""
|
||||
# Validate the version and parse it into pieces
|
||||
match = self._regex.fullmatch(version)
|
||||
if not match:
|
||||
raise InvalidVersion(f"Invalid version: {version!r}")
|
||||
self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
|
||||
self._release = tuple(map(int, match.group("release").split(".")))
|
||||
self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n"))
|
||||
self._post = _parse_letter_version(
|
||||
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
|
||||
)
|
||||
self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n"))
|
||||
self._local = _parse_local_version(match.group("local"))
|
||||
|
||||
# Key which will be used for sorting
|
||||
self._key_cache = None
|
||||
|
||||
def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
|
||||
epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
|
||||
release = (
|
||||
_validate_release(kwargs["release"])
|
||||
if "release" in kwargs
|
||||
else self._release
|
||||
)
|
||||
pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
|
||||
post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
|
||||
dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
|
||||
local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
|
||||
|
||||
if (
|
||||
epoch == self._epoch
|
||||
and release == self._release
|
||||
and pre == self._pre
|
||||
and post == self._post
|
||||
and dev == self._dev
|
||||
and local == self._local
|
||||
):
|
||||
return self
|
||||
|
||||
new_version = self.__class__.__new__(self.__class__)
|
||||
new_version._key_cache = None
|
||||
new_version._epoch = epoch
|
||||
new_version._release = release
|
||||
new_version._pre = pre
|
||||
new_version._post = post
|
||||
new_version._dev = dev
|
||||
new_version._local = local
|
||||
|
||||
return new_version
|
||||
|
||||
@property
|
||||
def _key(self) -> CmpKey:
|
||||
if self._key_cache is None:
|
||||
self._key_cache = _cmpkey(
|
||||
self._epoch,
|
||||
self._release,
|
||||
self._pre,
|
||||
self._post,
|
||||
self._dev,
|
||||
self._local,
|
||||
)
|
||||
return self._key_cache
|
||||
|
||||
@property
|
||||
@_deprecated("Version._version is private and will be removed soon")
|
||||
def _version(self) -> _Version:
|
||||
return _Version(
|
||||
self._epoch, self._release, self._dev, self._pre, self._post, self._local
|
||||
)
|
||||
|
||||
@_version.setter
|
||||
@_deprecated("Version._version is private and will be removed soon")
|
||||
def _version(self, value: _Version) -> None:
|
||||
self._epoch = value.epoch
|
||||
self._release = value.release
|
||||
self._dev = value.dev
|
||||
self._pre = value.pre
|
||||
self._post = value.post
|
||||
self._local = value.local
|
||||
self._key_cache = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""A representation of the Version that shows all internal state.
|
||||
|
||||
>>> Version('1.0.0')
|
||||
<Version('1.0.0')>
|
||||
"""
|
||||
return f"<Version('{self}')>"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""A string representation of the version that can be round-tripped.
|
||||
|
||||
>>> str(Version("1.0a5"))
|
||||
'1.0a5'
|
||||
"""
|
||||
# This is a hot function, so not calling self.base_version
|
||||
version = ".".join(map(str, self.release))
|
||||
|
||||
# Epoch
|
||||
if self.epoch:
|
||||
version = f"{self.epoch}!{version}"
|
||||
|
||||
# Pre-release
|
||||
if self.pre is not None:
|
||||
version += "".join(map(str, self.pre))
|
||||
|
||||
# Post-release
|
||||
if self.post is not None:
|
||||
version += f".post{self.post}"
|
||||
|
||||
# Development release
|
||||
if self.dev is not None:
|
||||
version += f".dev{self.dev}"
|
||||
|
||||
# Local version segment
|
||||
if self.local is not None:
|
||||
version += f"+{self.local}"
|
||||
|
||||
return version
|
||||
|
||||
@property
|
||||
def _str(self) -> str:
|
||||
"""Internal property for match_args"""
|
||||
return str(self)
|
||||
|
||||
@property
|
||||
def epoch(self) -> int:
|
||||
"""The epoch of the version.
|
||||
|
||||
>>> Version("2.0.0").epoch
|
||||
0
|
||||
>>> Version("1!2.0.0").epoch
|
||||
1
|
||||
"""
|
||||
return self._epoch
|
||||
|
||||
@property
|
||||
def release(self) -> tuple[int, ...]:
|
||||
"""The components of the "release" segment of the version.
|
||||
|
||||
>>> Version("1.2.3").release
|
||||
(1, 2, 3)
|
||||
>>> Version("2.0.0").release
|
||||
(2, 0, 0)
|
||||
>>> Version("1!2.0.0.post0").release
|
||||
(2, 0, 0)
|
||||
|
||||
Includes trailing zeroes but not the epoch or any pre-release / development /
|
||||
post-release suffixes.
|
||||
"""
|
||||
return self._release
|
||||
|
||||
@property
|
||||
def pre(self) -> tuple[str, int] | None:
|
||||
"""The pre-release segment of the version.
|
||||
|
||||
>>> print(Version("1.2.3").pre)
|
||||
None
|
||||
>>> Version("1.2.3a1").pre
|
||||
('a', 1)
|
||||
>>> Version("1.2.3b1").pre
|
||||
('b', 1)
|
||||
>>> Version("1.2.3rc1").pre
|
||||
('rc', 1)
|
||||
"""
|
||||
return self._pre
|
||||
|
||||
@property
|
||||
def post(self) -> int | None:
|
||||
"""The post-release number of the version.
|
||||
|
||||
>>> print(Version("1.2.3").post)
|
||||
None
|
||||
>>> Version("1.2.3.post1").post
|
||||
1
|
||||
"""
|
||||
return self._post[1] if self._post else None
|
||||
|
||||
@property
|
||||
def dev(self) -> int | None:
|
||||
"""The development number of the version.
|
||||
|
||||
>>> print(Version("1.2.3").dev)
|
||||
None
|
||||
>>> Version("1.2.3.dev1").dev
|
||||
1
|
||||
"""
|
||||
return self._dev[1] if self._dev else None
|
||||
|
||||
@property
|
||||
def local(self) -> str | None:
|
||||
"""The local version segment of the version.
|
||||
|
||||
>>> print(Version("1.2.3").local)
|
||||
None
|
||||
>>> Version("1.2.3+abc").local
|
||||
'abc'
|
||||
"""
|
||||
if self._local:
|
||||
return ".".join(str(x) for x in self._local)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def public(self) -> str:
|
||||
"""The public portion of the version.
|
||||
|
||||
>>> Version("1.2.3").public
|
||||
'1.2.3'
|
||||
>>> Version("1.2.3+abc").public
|
||||
'1.2.3'
|
||||
>>> Version("1!1.2.3dev1+abc").public
|
||||
'1!1.2.3.dev1'
|
||||
"""
|
||||
return str(self).split("+", 1)[0]
|
||||
|
||||
@property
|
||||
def base_version(self) -> str:
|
||||
"""The "base version" of the version.
|
||||
|
||||
>>> Version("1.2.3").base_version
|
||||
'1.2.3'
|
||||
>>> Version("1.2.3+abc").base_version
|
||||
'1.2.3'
|
||||
>>> Version("1!1.2.3dev1+abc").base_version
|
||||
'1!1.2.3'
|
||||
|
||||
The "base version" is the public version of the project without any pre or post
|
||||
release markers.
|
||||
"""
|
||||
release_segment = ".".join(map(str, self.release))
|
||||
return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
|
||||
|
||||
@property
|
||||
def is_prerelease(self) -> bool:
|
||||
"""Whether this version is a pre-release.
|
||||
|
||||
>>> Version("1.2.3").is_prerelease
|
||||
False
|
||||
>>> Version("1.2.3a1").is_prerelease
|
||||
True
|
||||
>>> Version("1.2.3b1").is_prerelease
|
||||
True
|
||||
>>> Version("1.2.3rc1").is_prerelease
|
||||
True
|
||||
>>> Version("1.2.3dev1").is_prerelease
|
||||
True
|
||||
"""
|
||||
return self.dev is not None or self.pre is not None
|
||||
|
||||
@property
|
||||
def is_postrelease(self) -> bool:
|
||||
"""Whether this version is a post-release.
|
||||
|
||||
>>> Version("1.2.3").is_postrelease
|
||||
False
|
||||
>>> Version("1.2.3.post1").is_postrelease
|
||||
True
|
||||
"""
|
||||
return self.post is not None
|
||||
|
||||
@property
|
||||
def is_devrelease(self) -> bool:
|
||||
"""Whether this version is a development release.
|
||||
|
||||
>>> Version("1.2.3").is_devrelease
|
||||
False
|
||||
>>> Version("1.2.3.dev1").is_devrelease
|
||||
True
|
||||
"""
|
||||
return self.dev is not None
|
||||
|
||||
@property
|
||||
def major(self) -> int:
|
||||
"""The first item of :attr:`release` or ``0`` if unavailable.
|
||||
|
||||
>>> Version("1.2.3").major
|
||||
1
|
||||
"""
|
||||
return self.release[0] if len(self.release) >= 1 else 0
|
||||
|
||||
@property
|
||||
def minor(self) -> int:
|
||||
"""The second item of :attr:`release` or ``0`` if unavailable.
|
||||
|
||||
>>> Version("1.2.3").minor
|
||||
2
|
||||
>>> Version("1").minor
|
||||
0
|
||||
"""
|
||||
return self.release[1] if len(self.release) >= 2 else 0
|
||||
|
||||
@property
|
||||
def micro(self) -> int:
|
||||
"""The third item of :attr:`release` or ``0`` if unavailable.
|
||||
|
||||
>>> Version("1.2.3").micro
|
||||
3
|
||||
>>> Version("1").micro
|
||||
0
|
||||
"""
|
||||
return self.release[2] if len(self.release) >= 3 else 0
|
||||
|
||||
|
||||
class _TrimmedRelease(Version):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, version: str | Version) -> None:
|
||||
if isinstance(version, Version):
|
||||
self._epoch = version._epoch
|
||||
self._release = version._release
|
||||
self._dev = version._dev
|
||||
self._pre = version._pre
|
||||
self._post = version._post
|
||||
self._local = version._local
|
||||
self._key_cache = version._key_cache
|
||||
return
|
||||
super().__init__(version) # pragma: no cover
|
||||
|
||||
@property
|
||||
def release(self) -> tuple[int, ...]:
|
||||
"""
|
||||
Release segment without any trailing zeros.
|
||||
|
||||
>>> _TrimmedRelease('1.0.0').release
|
||||
(1,)
|
||||
>>> _TrimmedRelease('0.0').release
|
||||
(0,)
|
||||
"""
|
||||
# This leaves one 0.
|
||||
rel = super().release
|
||||
len_release = len(rel)
|
||||
i = len_release
|
||||
while i > 1 and rel[i - 1] == 0:
|
||||
i -= 1
|
||||
return rel if i == len_release else rel[:i]
|
||||
|
||||
|
||||
def _parse_letter_version(
|
||||
letter: str | None, number: str | bytes | SupportsInt | None
|
||||
) -> tuple[str, int] | None:
|
||||
if letter:
|
||||
# We normalize any letters to their lower case form
|
||||
letter = letter.lower()
|
||||
|
||||
# We consider some words to be alternate spellings of other words and
|
||||
# in those cases we want to normalize the spellings to our preferred
|
||||
# spelling.
|
||||
letter = _LETTER_NORMALIZATION.get(letter, letter)
|
||||
|
||||
# We consider there to be an implicit 0 in a pre-release if there is
|
||||
# not a numeral associated with it.
|
||||
return letter, int(number or 0)
|
||||
|
||||
if number:
|
||||
# We assume if we are given a number, but we are not given a letter
|
||||
# then this is using the implicit post release syntax (e.g. 1.0-1)
|
||||
return "post", int(number)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
_local_version_separators = re.compile(r"[\._-]")
|
||||
|
||||
|
||||
def _parse_local_version(local: str | None) -> LocalType | None:
|
||||
"""
|
||||
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
||||
"""
|
||||
if local is not None:
|
||||
return tuple(
|
||||
part.lower() if not part.isdigit() else int(part)
|
||||
for part in _local_version_separators.split(local)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _cmpkey(
|
||||
epoch: int,
|
||||
release: tuple[int, ...],
|
||||
pre: tuple[str, int] | None,
|
||||
post: tuple[str, int] | None,
|
||||
dev: tuple[str, int] | None,
|
||||
local: LocalType | None,
|
||||
) -> CmpKey:
|
||||
# When we compare a release version, we want to compare it with all of the
|
||||
# trailing zeros removed. We will use this for our sorting key.
|
||||
len_release = len(release)
|
||||
i = len_release
|
||||
while i and release[i - 1] == 0:
|
||||
i -= 1
|
||||
_release = release if i == len_release else release[:i]
|
||||
|
||||
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
|
||||
# We'll do this by abusing the pre segment, but we _only_ want to do this
|
||||
# if there is not a pre or a post segment. If we have one of those then
|
||||
# the normal sorting rules will handle this case correctly.
|
||||
if pre is None and post is None and dev is not None:
|
||||
_pre: CmpPrePostDevType = NegativeInfinity
|
||||
# Versions without a pre-release (except as noted above) should sort after
|
||||
# those with one.
|
||||
elif pre is None:
|
||||
_pre = Infinity
|
||||
else:
|
||||
_pre = pre
|
||||
|
||||
# Versions without a post segment should sort before those with one.
|
||||
if post is None:
|
||||
_post: CmpPrePostDevType = NegativeInfinity
|
||||
|
||||
else:
|
||||
_post = post
|
||||
|
||||
# Versions without a development segment should sort after those with one.
|
||||
if dev is None:
|
||||
_dev: CmpPrePostDevType = Infinity
|
||||
|
||||
else:
|
||||
_dev = dev
|
||||
|
||||
if local is None:
|
||||
# Versions without a local segment should sort before those with one.
|
||||
_local: CmpLocalType = NegativeInfinity
|
||||
else:
|
||||
# Versions with a local segment need that segment parsed to implement
|
||||
# the sorting rules in PEP440.
|
||||
# - Alpha numeric segments sort before numeric segments
|
||||
# - Alpha numeric segments sort lexicographically
|
||||
# - Numeric segments sort numerically
|
||||
# - Shorter versions sort before longer versions when the prefixes
|
||||
# match exactly
|
||||
_local = tuple(
|
||||
(i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
|
||||
)
|
||||
|
||||
return epoch, _release, _pre, _post, _dev, _local
|
||||
Reference in New Issue
Block a user