Загрузить файлы в «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