[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2024-04-13 00:00:18 +00:00
parent 72ad6dc953
commit f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions

View file

@ -1,26 +1,27 @@
# 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
__all__ = [
"__title__",
"__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
'__title__',
'__summary__',
'__uri__',
'__version__',
'__author__',
'__email__',
'__license__',
'__copyright__',
]
__title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
__title__ = 'packaging'
__summary__ = 'Core utilities for Python packages'
__uri__ = 'https://github.com/pypa/packaging'
__version__ = "21.3"
__version__ = '21.3'
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__author__ = 'Donald Stufft and individual contributors'
__email__ = 'donald@stufft.io'
__license__ = "BSD-2-Clause or Apache-2.0"
__copyright__ = "2014-2019 %s" % __author__
__license__ = 'BSD-2-Clause or Apache-2.0'
__copyright__ = '2014-2019 %s' % __author__

View file

@ -1,25 +1,24 @@
# 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
from .__about__ import (
__author__,
__copyright__,
__email__,
__license__,
__summary__,
__title__,
__uri__,
__version__,
)
from .__about__ import __author__
from .__about__ import __copyright__
from .__about__ import __email__
from .__about__ import __license__
from .__about__ import __summary__
from .__about__ import __title__
from .__about__ import __uri__
from .__about__ import __version__
__all__ = [
"__title__",
"__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
'__title__',
'__summary__',
'__uri__',
'__version__',
'__author__',
'__email__',
'__license__',
'__copyright__',
]

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import collections
import functools
import os
@ -5,7 +7,12 @@ import re
import struct
import sys
import warnings
from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple
from typing import Dict
from typing import IO
from typing import Iterator
from typing import NamedTuple
from typing import Optional
from typing import Tuple
# Python does not provide platform information at sufficient granularity to
@ -36,27 +43,27 @@ class _ELFFileHeader:
def unpack(fmt: str) -> int:
try:
data = file.read(struct.calcsize(fmt))
result: Tuple[int, ...] = struct.unpack(fmt, data)
result: tuple[int, ...] = struct.unpack(fmt, data)
except struct.error:
raise _ELFFileHeader._InvalidELFFileHeader()
return result[0]
self.e_ident_magic = unpack(">I")
self.e_ident_magic = unpack('>I')
if self.e_ident_magic != self.ELF_MAGIC_NUMBER:
raise _ELFFileHeader._InvalidELFFileHeader()
self.e_ident_class = unpack("B")
self.e_ident_class = unpack('B')
if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}:
raise _ELFFileHeader._InvalidELFFileHeader()
self.e_ident_data = unpack("B")
self.e_ident_data = unpack('B')
if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}:
raise _ELFFileHeader._InvalidELFFileHeader()
self.e_ident_version = unpack("B")
self.e_ident_osabi = unpack("B")
self.e_ident_abiversion = unpack("B")
self.e_ident_version = unpack('B')
self.e_ident_osabi = unpack('B')
self.e_ident_abiversion = unpack('B')
self.e_ident_pad = file.read(7)
format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H"
format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q"
format_h = '<H' if self.e_ident_data == self.ELFDATA2LSB else '>H'
format_i = '<I' if self.e_ident_data == self.ELFDATA2LSB else '>I'
format_q = '<Q' if self.e_ident_data == self.ELFDATA2LSB else '>Q'
format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q
self.e_type = unpack(format_h)
self.e_machine = unpack(format_h)
@ -73,9 +80,9 @@ class _ELFFileHeader:
self.e_shstrndx = unpack(format_h)
def _get_elf_header() -> Optional[_ELFFileHeader]:
def _get_elf_header() -> _ELFFileHeader | None:
try:
with open(sys.executable, "rb") as f:
with open(sys.executable, 'rb') as f:
elf_header = _ELFFileHeader(f)
except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
return None
@ -112,11 +119,11 @@ def _is_linux_i686() -> bool:
def _have_compatible_abi(arch: str) -> bool:
if arch == "armv7l":
if arch == 'armv7l':
return _is_linux_armhf()
if arch == "i686":
if arch == 'i686':
return _is_linux_i686()
return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"}
return arch in {'x86_64', 'aarch64', 'ppc64', 'ppc64le', 's390x'}
# If glibc ever changes its major version, we need to know what the last
@ -124,7 +131,7 @@ def _have_compatible_abi(arch: str) -> bool:
# For now, guess what the highest minor version might be, assume it will
# be 50 for testing. Once this actually happens, update the dictionary
# with the actual value.
_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50)
_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50)
class _GLibCVersion(NamedTuple):
@ -132,7 +139,7 @@ class _GLibCVersion(NamedTuple):
minor: int
def _glibc_version_string_confstr() -> Optional[str]:
def _glibc_version_string_confstr() -> str | None:
"""
Primary implementation of glibc_version_string using os.confstr.
"""
@ -142,7 +149,7 @@ def _glibc_version_string_confstr() -> Optional[str]:
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
try:
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17".
version_string = os.confstr("CS_GNU_LIBC_VERSION")
version_string = os.confstr('CS_GNU_LIBC_VERSION')
assert version_string is not None
_, version = version_string.split()
except (AssertionError, AttributeError, OSError, ValueError):
@ -151,7 +158,7 @@ def _glibc_version_string_confstr() -> Optional[str]:
return version
def _glibc_version_string_ctypes() -> Optional[str]:
def _glibc_version_string_ctypes() -> str | None:
"""
Fallback implementation of glibc_version_string using ctypes.
"""
@ -190,17 +197,17 @@ def _glibc_version_string_ctypes() -> Optional[str]:
version_str: str = gnu_get_libc_version()
# py2 / py3 compatibility:
if not isinstance(version_str, str):
version_str = version_str.decode("ascii")
version_str = version_str.decode('ascii')
return version_str
def _glibc_version_string() -> Optional[str]:
def _glibc_version_string() -> str | None:
"""Returns glibc version string, or None if not using glibc."""
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
def _parse_glibc_version(version_str: str) -> tuple[int, int]:
"""Parse glibc version.
We use a regexp instead of str.split because we want to discard any
@ -208,19 +215,19 @@ def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
in patched/forked versions of glibc (e.g. Linaro's version of glibc
uses version strings like "2.20-2014.11"). See gh-3588.
"""
m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
m = re.match(r'(?P<major>[0-9]+)\.(?P<minor>[0-9]+)', version_str)
if not m:
warnings.warn(
"Expected glibc version with 2 components major.minor,"
" got: %s" % version_str,
'Expected glibc version with 2 components major.minor,'
' got: %s' % version_str,
RuntimeWarning,
)
return -1, -1
return int(m.group("major")), int(m.group("minor"))
return int(m.group('major')), int(m.group('minor'))
@functools.lru_cache()
def _get_glibc_version() -> Tuple[int, int]:
@functools.lru_cache
def _get_glibc_version() -> tuple[int, int]:
version_str = _glibc_version_string()
if version_str is None:
return (-1, -1)
@ -237,30 +244,30 @@ def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool:
import _manylinux # noqa
except ImportError:
return True
if hasattr(_manylinux, "manylinux_compatible"):
if hasattr(_manylinux, 'manylinux_compatible'):
result = _manylinux.manylinux_compatible(version[0], version[1], arch)
if result is not None:
return bool(result)
return True
if version == _GLibCVersion(2, 5):
if hasattr(_manylinux, "manylinux1_compatible"):
if hasattr(_manylinux, 'manylinux1_compatible'):
return bool(_manylinux.manylinux1_compatible)
if version == _GLibCVersion(2, 12):
if hasattr(_manylinux, "manylinux2010_compatible"):
if hasattr(_manylinux, 'manylinux2010_compatible'):
return bool(_manylinux.manylinux2010_compatible)
if version == _GLibCVersion(2, 17):
if hasattr(_manylinux, "manylinux2014_compatible"):
if hasattr(_manylinux, 'manylinux2014_compatible'):
return bool(_manylinux.manylinux2014_compatible)
return True
_LEGACY_MANYLINUX_MAP = {
# CentOS 7 w/ glibc 2.17 (PEP 599)
(2, 17): "manylinux2014",
(2, 17): 'manylinux2014',
# CentOS 6 w/ glibc 2.12 (PEP 571)
(2, 12): "manylinux2010",
(2, 12): 'manylinux2010',
# CentOS 5 w/ glibc 2.5 (PEP 513)
(2, 5): "manylinux1",
(2, 5): 'manylinux1',
}
@ -269,7 +276,7 @@ def platform_tags(linux: str, arch: str) -> Iterator[str]:
return
# Oldest glibc to be supported regardless of architecture is (2, 17).
too_old_glibc2 = _GLibCVersion(2, 16)
if arch in {"x86_64", "i686"}:
if arch in {'x86_64', 'i686'}:
# On x86/i686 also oldest glibc to be supported is (2, 5).
too_old_glibc2 = _GLibCVersion(2, 4)
current_glibc = _GLibCVersion(*_get_glibc_version())
@ -291,11 +298,11 @@ def platform_tags(linux: str, arch: str) -> Iterator[str]:
min_minor = -1
for glibc_minor in range(glibc_max.minor, min_minor, -1):
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
tag = "manylinux_{}_{}".format(*glibc_version)
tag = 'manylinux_{}_{}'.format(*glibc_version)
if _is_compatible(tag, arch, glibc_version):
yield linux.replace("linux", tag)
yield linux.replace('linux', tag)
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
if glibc_version in _LEGACY_MANYLINUX_MAP:
legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
if _is_compatible(legacy_tag, arch, glibc_version):
yield linux.replace("linux", legacy_tag)
yield linux.replace('linux', legacy_tag)

View file

@ -3,6 +3,7 @@
This module implements logic to detect if the currently running Python is
linked against musl, and what musl version is used.
"""
from __future__ import annotations
import contextlib
import functools
@ -12,14 +13,18 @@ import re
import struct
import subprocess
import sys
from typing import IO, Iterator, NamedTuple, Optional, Tuple
from typing import IO
from typing import Iterator
from typing import NamedTuple
from typing import Optional
from typing import Tuple
def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]:
def _read_unpacked(f: IO[bytes], fmt: str) -> tuple[int, ...]:
return struct.unpack(fmt, f.read(struct.calcsize(fmt)))
def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]:
def _parse_ld_musl_from_elf(f: IO[bytes]) -> str | None:
"""Detect musl libc location by parsing the Python executable.
Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
@ -27,20 +32,20 @@ def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]:
"""
f.seek(0)
try:
ident = _read_unpacked(f, "16B")
ident = _read_unpacked(f, '16B')
except struct.error:
return None
if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF.
if ident[:4] != tuple(b'\x7fELF'): # Invalid magic, not ELF.
return None
f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version.
f.seek(struct.calcsize('HHI'), 1) # Skip file type, machine, and version.
try:
# e_fmt: Format for program header.
# p_fmt: Format for section header.
# p_idx: Indexes to find p_type, p_offset, and p_filesz.
e_fmt, p_fmt, p_idx = {
1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit.
2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit.
1: ('IIIIHHH', 'IIIIIIII', (0, 1, 4)), # 32-bit.
2: ('QQQIHHH', 'IIQQQQQQ', (0, 2, 5)), # 64-bit.
}[ident[4]]
except KeyError:
return None
@ -61,8 +66,8 @@ def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]:
if p_type != 3: # Not PT_INTERP.
continue
f.seek(p_offset)
interpreter = os.fsdecode(f.read(p_filesz)).strip("\0")
if "musl" not in interpreter:
interpreter = os.fsdecode(f.read(p_filesz)).strip('\0')
if 'musl' not in interpreter:
return None
return interpreter
return None
@ -73,18 +78,18 @@ class _MuslVersion(NamedTuple):
minor: int
def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
def _parse_musl_version(output: str) -> _MuslVersion | None:
lines = [n for n in (n.strip() for n in output.splitlines()) if n]
if len(lines) < 2 or lines[0][:4] != "musl":
if len(lines) < 2 or lines[0][:4] != 'musl':
return None
m = re.match(r"Version (\d+)\.(\d+)", lines[1])
m = re.match(r'Version (\d+)\.(\d+)', lines[1])
if not m:
return None
return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
@functools.lru_cache()
def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
@functools.lru_cache
def _get_musl_version(executable: str) -> _MuslVersion | None:
"""Detect currently-running musl runtime version.
This is done by checking the specified executable's dynamic linking
@ -97,13 +102,13 @@ def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
"""
with contextlib.ExitStack() as stack:
try:
f = stack.enter_context(open(executable, "rb"))
f = stack.enter_context(open(executable, 'rb'))
except OSError:
return None
ld = _parse_ld_musl_from_elf(f)
if not ld:
return None
proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True)
proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
return _parse_musl_version(proc.stderr)
@ -120,17 +125,17 @@ def platform_tags(arch: str) -> Iterator[str]:
if sys_musl is None: # Python not dynamically linked against musl.
return
for minor in range(sys_musl.minor, -1, -1):
yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
yield f'musllinux_{sys_musl.major}_{minor}_{arch}'
if __name__ == "__main__": # pragma: no cover
if __name__ == '__main__': # pragma: no cover
import sysconfig
plat = sysconfig.get_platform()
assert plat.startswith("linux-"), "not linux"
assert plat.startswith('linux-'), 'not linux'
print("plat:", plat)
print("musl:", _get_musl_version(sys.executable))
print("tags:", end=" ")
for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
print(t, end="\n ")
print('plat:', plat)
print('musl:', _get_musl_version(sys.executable))
print('tags:', end=' ')
for t in platform_tags(re.sub(r'[.-]', '_', plat.split('-', 1)[-1])):
print(t, end='\n ')

View file

@ -1,11 +1,12 @@
# 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
class InfinityType:
def __repr__(self) -> str:
return "Infinity"
return 'Infinity'
def __hash__(self) -> int:
return hash(repr(self))
@ -25,7 +26,7 @@ class InfinityType:
def __ge__(self, other: object) -> bool:
return True
def __neg__(self: object) -> "NegativeInfinityType":
def __neg__(self: object) -> NegativeInfinityType:
return NegativeInfinity
@ -34,7 +35,7 @@ Infinity = InfinityType()
class NegativeInfinityType:
def __repr__(self) -> str:
return "-Infinity"
return '-Infinity'
def __hash__(self) -> int:
return hash(repr(self))

View file

@ -1,33 +1,39 @@
# 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 operator
import os
import platform
import sys
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
from pip._vendor.pyparsing import ( # noqa: N817
Forward,
Group,
Literal as L,
ParseException,
ParseResults,
QuotedString,
ZeroOrMore,
stringEnd,
stringStart,
)
from pip._vendor.pyparsing import Forward
from pip._vendor.pyparsing import Group
from pip._vendor.pyparsing import Literal as L
from pip._vendor.pyparsing import ParseException
from pip._vendor.pyparsing import ParseResults
from pip._vendor.pyparsing import QuotedString
from pip._vendor.pyparsing import stringEnd
from pip._vendor.pyparsing import stringStart
from pip._vendor.pyparsing import ZeroOrMore
from .specifiers import InvalidSpecifier, Specifier
from .specifiers import InvalidSpecifier
from .specifiers import Specifier
__all__ = [
"InvalidMarker",
"UndefinedComparison",
"UndefinedEnvironmentName",
"Marker",
"default_environment",
'InvalidMarker',
'UndefinedComparison',
'UndefinedEnvironmentName',
'Marker',
'default_environment',
]
Operator = Callable[[str, str], bool]
@ -82,54 +88,54 @@ class Op(Node):
VARIABLE = (
L("implementation_version")
| L("platform_python_implementation")
| L("implementation_name")
| L("python_full_version")
| L("platform_release")
| L("platform_version")
| L("platform_machine")
| L("platform_system")
| L("python_version")
| L("sys_platform")
| L("os_name")
| L("os.name") # PEP-345
| L("sys.platform") # PEP-345
| L("platform.version") # PEP-345
| L("platform.machine") # PEP-345
| L("platform.python_implementation") # PEP-345
| L("python_implementation") # undocumented setuptools legacy
| L("extra") # PEP-508
L('implementation_version') |
L('platform_python_implementation') |
L('implementation_name') |
L('python_full_version') |
L('platform_release') |
L('platform_version') |
L('platform_machine') |
L('platform_system') |
L('python_version') |
L('sys_platform') |
L('os_name') |
L('os.name') | # PEP-345
L('sys.platform') | # PEP-345
L('platform.version') | # PEP-345
L('platform.machine') | # PEP-345
L('platform.python_implementation') | # PEP-345
L('python_implementation') | # undocumented setuptools legacy
L('extra') # PEP-508
)
ALIASES = {
"os.name": "os_name",
"sys.platform": "sys_platform",
"platform.version": "platform_version",
"platform.machine": "platform_machine",
"platform.python_implementation": "platform_python_implementation",
"python_implementation": "platform_python_implementation",
'os.name': 'os_name',
'sys.platform': 'sys_platform',
'platform.version': 'platform_version',
'platform.machine': 'platform_machine',
'platform.python_implementation': 'platform_python_implementation',
'python_implementation': 'platform_python_implementation',
}
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = (
L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
L('===') | L('==') | L('>=') | L('<=') | L('!=') | L('~=') | L('>') | L('<')
)
MARKER_OP = VERSION_CMP | L("not in") | L("in")
MARKER_OP = VERSION_CMP | L('not in') | L('in')
MARKER_OP.setParseAction(lambda s, l, t: Op(t[0]))
MARKER_VALUE = QuotedString("'") | QuotedString('"')
MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0]))
BOOLOP = L("and") | L("or")
BOOLOP = L('and') | L('or')
MARKER_VAR = VARIABLE | MARKER_VALUE
MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR)
MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0]))
LPAREN = L("(").suppress()
RPAREN = L(")").suppress()
LPAREN = L('(').suppress()
RPAREN = L(')').suppress()
MARKER_EXPR = Forward()
MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN)
@ -138,7 +144,7 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
MARKER = stringStart + MARKER_EXPR + stringEnd
def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]:
def _coerce_parse_result(results: ParseResults | list[Any]) -> list[Any]:
if isinstance(results, ParseResults):
return [_coerce_parse_result(i) for i in results]
else:
@ -146,7 +152,7 @@ def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]:
def _format_marker(
marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True
marker: list[str] | tuple[Node, ...] | str, first: bool | None = True,
) -> str:
assert isinstance(marker, (list, tuple, str))
@ -156,47 +162,47 @@ def _format_marker(
# the rest of this function so that we don't get extraneous () on the
# outside.
if (
isinstance(marker, list)
and len(marker) == 1
and isinstance(marker[0], (list, tuple))
isinstance(marker, list) and
len(marker) == 1 and
isinstance(marker[0], (list, tuple))
):
return _format_marker(marker[0])
if isinstance(marker, list):
inner = (_format_marker(m, first=False) for m in marker)
if first:
return " ".join(inner)
return ' '.join(inner)
else:
return "(" + " ".join(inner) + ")"
return '(' + ' '.join(inner) + ')'
elif isinstance(marker, tuple):
return " ".join([m.serialize() for m in marker])
return ' '.join([m.serialize() for m in marker])
else:
return marker
_operators: Dict[str, Operator] = {
"in": lambda lhs, rhs: lhs in rhs,
"not in": lambda lhs, rhs: lhs not in rhs,
"<": operator.lt,
"<=": operator.le,
"==": operator.eq,
"!=": operator.ne,
">=": operator.ge,
">": operator.gt,
_operators: dict[str, Operator] = {
'in': lambda lhs, rhs: lhs in rhs,
'not in': lambda lhs, rhs: lhs not in rhs,
'<': operator.lt,
'<=': operator.le,
'==': operator.eq,
'!=': operator.ne,
'>=': operator.ge,
'>': operator.gt,
}
def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
try:
spec = Specifier("".join([op.serialize(), rhs]))
spec = Specifier(''.join([op.serialize(), rhs]))
except InvalidSpecifier:
pass
else:
return spec.contains(lhs)
oper: Optional[Operator] = _operators.get(op.serialize())
oper: Operator | None = _operators.get(op.serialize())
if oper is None:
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
raise UndefinedComparison(f'Undefined {op!r} on {lhs!r} and {rhs!r}.')
return oper(lhs, rhs)
@ -208,19 +214,19 @@ class Undefined:
_undefined = Undefined()
def _get_env(environment: Dict[str, str], name: str) -> str:
value: Union[str, Undefined] = environment.get(name, _undefined)
def _get_env(environment: dict[str, str], name: str) -> str:
value: str | Undefined = environment.get(name, _undefined)
if isinstance(value, Undefined):
raise UndefinedEnvironmentName(
f"{name!r} does not exist in evaluation environment."
f'{name!r} does not exist in evaluation environment.',
)
return value
def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool:
groups: List[List[bool]] = [[]]
def _evaluate_markers(markers: list[Any], environment: dict[str, str]) -> bool:
groups: list[list[bool]] = [[]]
for marker in markers:
assert isinstance(marker, (list, tuple, str))
@ -239,36 +245,36 @@ def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool:
groups[-1].append(_eval_op(lhs_value, op, rhs_value))
else:
assert marker in ["and", "or"]
if marker == "or":
assert marker in ['and', 'or']
if marker == 'or':
groups.append([])
return any(all(item) for item in groups)
def format_full_version(info: "sys._version_info") -> str:
version = "{0.major}.{0.minor}.{0.micro}".format(info)
def format_full_version(info: sys._version_info) -> str:
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
kind = info.releaselevel
if kind != "final":
if kind != 'final':
version += kind[0] + str(info.serial)
return version
def default_environment() -> Dict[str, str]:
def default_environment() -> dict[str, str]:
iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
return {
"implementation_name": implementation_name,
"implementation_version": iver,
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
'implementation_name': implementation_name,
'implementation_version': iver,
'os_name': os.name,
'platform_machine': platform.machine(),
'platform_release': platform.release(),
'platform_system': platform.system(),
'platform_version': platform.version(),
'python_full_version': platform.python_version(),
'platform_python_implementation': platform.python_implementation(),
'python_version': '.'.join(platform.python_version_tuple()[:2]),
'sys_platform': sys.platform,
}
@ -278,8 +284,8 @@ class Marker:
self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e:
raise InvalidMarker(
f"Invalid marker: {marker!r}, parse error at "
f"{marker[e.loc : e.loc + 8]!r}"
f'Invalid marker: {marker!r}, parse error at '
f'{marker[e.loc : e.loc + 8]!r}',
)
def __str__(self) -> str:
@ -288,7 +294,7 @@ class Marker:
def __repr__(self) -> str:
return f"<Marker('{self}')>"
def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool:
def evaluate(self, environment: dict[str, str] | None = None) -> bool:
"""Evaluate a marker.
Return the boolean from evaluating the given marker against the

View file

@ -1,27 +1,31 @@
# 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
import string
import urllib.parse
from typing import List, Optional as TOptional, Set
from typing import List
from typing import Optional as TOptional
from typing import Set
from pip._vendor.pyparsing import ( # noqa
Combine,
Literal as L,
Optional,
ParseException,
Regex,
Word,
ZeroOrMore,
originalTextFor,
stringEnd,
stringStart,
)
from pip._vendor.pyparsing import Combine
from pip._vendor.pyparsing import Literal as L
from pip._vendor.pyparsing import Optional
from pip._vendor.pyparsing import originalTextFor
from pip._vendor.pyparsing import ParseException
from pip._vendor.pyparsing import Regex
from pip._vendor.pyparsing import stringEnd
from pip._vendor.pyparsing import stringStart
from pip._vendor.pyparsing import Word
from pip._vendor.pyparsing import ZeroOrMore
from .markers import MARKER_EXPR, Marker
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
from .markers import Marker
from .markers import MARKER_EXPR
from .specifiers import LegacySpecifier
from .specifiers import Specifier
from .specifiers import SpecifierSet
class InvalidRequirement(ValueError):
@ -32,43 +36,43 @@ class InvalidRequirement(ValueError):
ALPHANUM = Word(string.ascii_letters + string.digits)
LBRACKET = L("[").suppress()
RBRACKET = L("]").suppress()
LPAREN = L("(").suppress()
RPAREN = L(")").suppress()
COMMA = L(",").suppress()
SEMICOLON = L(";").suppress()
AT = L("@").suppress()
LBRACKET = L('[').suppress()
RBRACKET = L(']').suppress()
LPAREN = L('(').suppress()
RPAREN = L(')').suppress()
COMMA = L(',').suppress()
SEMICOLON = L(';').suppress()
AT = L('@').suppress()
PUNCTUATION = Word("-_.")
PUNCTUATION = Word('-_.')
IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM)
IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
NAME = IDENTIFIER("name")
NAME = IDENTIFIER('name')
EXTRA = IDENTIFIER
URI = Regex(r"[^ ]+")("url")
URI = Regex(r'[^ ]+')('url')
URL = AT + URI
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)('extras')
VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
VERSION_MANY = Combine(
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
)("_raw_spec")
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=',', adjacent=False,
)('_raw_spec')
_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '')
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)('specifier')
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR = originalTextFor(MARKER_EXPR())('marker')
MARKER_EXPR.setParseAction(
lambda s, l, t: Marker(s[t._original_start : t._original_end])
lambda s, l, t: Marker(s[t._original_start: t._original_end]),
)
MARKER_SEPARATOR = SEMICOLON
MARKER = MARKER_SEPARATOR + MARKER_EXPR
@ -81,7 +85,7 @@ NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARK
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
# pyparsing isn't thread safe during initialization, so we do it eagerly, see
# issue #104
REQUIREMENT.parseString("x[]")
REQUIREMENT.parseString('x[]')
class Requirement:
@ -102,45 +106,45 @@ class Requirement:
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement(
f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}'
f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}',
)
self.name: str = req.name
if req.url:
parsed_url = urllib.parse.urlparse(req.url)
if parsed_url.scheme == "file":
if parsed_url.scheme == 'file':
if urllib.parse.urlunparse(parsed_url) != req.url:
raise InvalidRequirement("Invalid URL given")
raise InvalidRequirement('Invalid URL given')
elif not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc
):
raise InvalidRequirement(f"Invalid URL: {req.url}")
raise InvalidRequirement(f'Invalid URL: {req.url}')
self.url: TOptional[str] = req.url
else:
self.url = None
self.extras: Set[str] = set(req.extras.asList() if req.extras else [])
self.extras: set[str] = set(req.extras.asList() if req.extras else [])
self.specifier: SpecifierSet = SpecifierSet(req.specifier)
self.marker: TOptional[Marker] = req.marker if req.marker else None
def __str__(self) -> str:
parts: List[str] = [self.name]
parts: list[str] = [self.name]
if self.extras:
formatted_extras = ",".join(sorted(self.extras))
parts.append(f"[{formatted_extras}]")
formatted_extras = ','.join(sorted(self.extras))
parts.append(f'[{formatted_extras}]')
if self.specifier:
parts.append(str(self.specifier))
if self.url:
parts.append(f"@ {self.url}")
parts.append(f'@ {self.url}')
if self.marker:
parts.append(" ")
parts.append(' ')
if self.marker:
parts.append(f"; {self.marker}")
parts.append(f'; {self.marker}')
return "".join(parts)
return ''.join(parts)
def __repr__(self) -> str:
return f"<Requirement('{self}')>"

View file

@ -1,32 +1,33 @@
# 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 abc
import functools
import itertools
import re
import warnings
from typing import (
Callable,
Dict,
Iterable,
Iterator,
List,
Optional,
Pattern,
Set,
Tuple,
TypeVar,
Union,
)
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import Pattern
from typing import Set
from typing import Tuple
from typing import TypeVar
from typing import Union
from .utils import canonicalize_version
from .version import LegacyVersion, Version, parse
from .version import LegacyVersion
from .version import parse
from .version import Version
ParsedVersion = Union[Version, LegacyVersion]
UnparsedVersion = Union[Version, LegacyVersion, str]
VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion)
VersionTypeVar = TypeVar('VersionTypeVar', bound=UnparsedVersion)
CallableOperator = Callable[[ParsedVersion, str], bool]
@ -58,7 +59,7 @@ class BaseSpecifier(metaclass=abc.ABCMeta):
"""
@abc.abstractproperty
def prereleases(self) -> Optional[bool]:
def prereleases(self) -> bool | None:
"""
Returns whether or not pre-releases as a whole are allowed by this
specifier.
@ -72,14 +73,14 @@ class BaseSpecifier(metaclass=abc.ABCMeta):
"""
@abc.abstractmethod
def contains(self, item: str, prereleases: Optional[bool] = None) -> bool:
def contains(self, item: str, prereleases: bool | None = None) -> bool:
"""
Determines if the given item is contained within this specifier.
"""
@abc.abstractmethod
def filter(
self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
self, iterable: Iterable[VersionTypeVar], prereleases: bool | None = None,
) -> Iterable[VersionTypeVar]:
"""
Takes an iterable of items and filters them so that only items which
@ -89,17 +90,17 @@ class BaseSpecifier(metaclass=abc.ABCMeta):
class _IndividualSpecifier(BaseSpecifier):
_operators: Dict[str, str] = {}
_operators: dict[str, str] = {}
_regex: Pattern[str]
def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
def __init__(self, spec: str = '', prereleases: bool | None = None) -> None:
match = self._regex.search(spec)
if not match:
raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
self._spec: Tuple[str, str] = (
match.group("operator").strip(),
match.group("version").strip(),
self._spec: tuple[str, str] = (
match.group('operator').strip(),
match.group('version').strip(),
)
# Store whether or not this Specifier should accept prereleases
@ -107,18 +108,18 @@ class _IndividualSpecifier(BaseSpecifier):
def __repr__(self) -> str:
pre = (
f", prereleases={self.prereleases!r}"
f', prereleases={self.prereleases!r}'
if self._prereleases is not None
else ""
else ''
)
return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
return f'<{self.__class__.__name__}({str(self)!r}{pre})>'
def __str__(self) -> str:
return "{}{}".format(*self._spec)
return '{}{}'.format(*self._spec)
@property
def _canonical_spec(self) -> Tuple[str, str]:
def _canonical_spec(self) -> tuple[str, str]:
return self._spec[0], canonicalize_version(self._spec[1])
def __hash__(self) -> int:
@ -137,7 +138,7 @@ class _IndividualSpecifier(BaseSpecifier):
def _get_operator(self, op: str) -> CallableOperator:
operator_callable: CallableOperator = getattr(
self, f"_compare_{self._operators[op]}"
self, f'_compare_{self._operators[op]}',
)
return operator_callable
@ -155,7 +156,7 @@ class _IndividualSpecifier(BaseSpecifier):
return self._spec[1]
@property
def prereleases(self) -> Optional[bool]:
def prereleases(self) -> bool | None:
return self._prereleases
@prereleases.setter
@ -166,7 +167,7 @@ class _IndividualSpecifier(BaseSpecifier):
return self.contains(item)
def contains(
self, item: UnparsedVersion, prereleases: Optional[bool] = None
self, item: UnparsedVersion, prereleases: bool | None = None,
) -> bool:
# Determine if prereleases are to be allowed or not.
@ -189,13 +190,13 @@ class _IndividualSpecifier(BaseSpecifier):
return operator_callable(normalized_item, self.version)
def filter(
self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
self, iterable: Iterable[VersionTypeVar], prereleases: bool | None = None,
) -> Iterable[VersionTypeVar]:
yielded = False
found_prereleases = []
kw = {"prereleases": prereleases if prereleases is not None else True}
kw = {'prereleases': prereleases if prereleases is not None else True}
# Attempt to iterate over all the values in the iterable and if any of
# them match, yield them.
@ -238,23 +239,23 @@ class LegacySpecifier(_IndividualSpecifier):
)
"""
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_regex = re.compile(r'^\s*' + _regex_str + r'\s*$', re.VERBOSE | re.IGNORECASE)
_operators = {
"==": "equal",
"!=": "not_equal",
"<=": "less_than_equal",
">=": "greater_than_equal",
"<": "less_than",
">": "greater_than",
'==': 'equal',
'!=': 'not_equal',
'<=': 'less_than_equal',
'>=': 'greater_than_equal',
'<': 'less_than',
'>': 'greater_than',
}
def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
def __init__(self, spec: str = '', prereleases: bool | None = None) -> None:
super().__init__(spec, prereleases)
warnings.warn(
"Creating a LegacyVersion has been deprecated and will be "
"removed in the next major release",
'Creating a LegacyVersion has been deprecated and will be '
'removed in the next major release',
DeprecationWarning,
)
@ -273,7 +274,7 @@ class LegacySpecifier(_IndividualSpecifier):
return prospective <= self._coerce_version(spec)
def _compare_greater_than_equal(
self, prospective: LegacyVersion, spec: str
self, prospective: LegacyVersion, spec: str,
) -> bool:
return prospective >= self._coerce_version(spec)
@ -285,10 +286,10 @@ class LegacySpecifier(_IndividualSpecifier):
def _require_version_compare(
fn: Callable[["Specifier", ParsedVersion, str], bool]
) -> Callable[["Specifier", ParsedVersion, str], bool]:
fn: Callable[[Specifier, ParsedVersion, str], bool],
) -> Callable[[Specifier, ParsedVersion, str], bool]:
@functools.wraps(fn)
def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool:
def wrapped(self: Specifier, prospective: ParsedVersion, spec: str) -> bool:
if not isinstance(prospective, Version):
return False
return fn(self, prospective, spec)
@ -391,17 +392,17 @@ class Specifier(_IndividualSpecifier):
)
"""
_regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_regex = re.compile(r'^\s*' + _regex_str + r'\s*$', re.VERBOSE | re.IGNORECASE)
_operators = {
"~=": "compatible",
"==": "equal",
"!=": "not_equal",
"<=": "less_than_equal",
">=": "greater_than_equal",
"<": "less_than",
">": "greater_than",
"===": "arbitrary",
'~=': 'compatible',
'==': 'equal',
'!=': 'not_equal',
'<=': 'less_than_equal',
'>=': 'greater_than_equal',
'<': 'less_than',
'>': 'greater_than',
'===': 'arbitrary',
}
@_require_version_compare
@ -415,22 +416,22 @@ class Specifier(_IndividualSpecifier):
# We want everything but the last item in the version, but we want to
# ignore suffix segments.
prefix = ".".join(
list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
prefix = '.'.join(
list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1],
)
# Add the prefix notation to the end of our string
prefix += ".*"
prefix += '.*'
return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
prospective, prefix
return self._get_operator('>=')(prospective, spec) and self._get_operator('==')(
prospective, prefix,
)
@_require_version_compare
def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool:
# We need special logic to handle prefix matching
if spec.endswith(".*"):
if spec.endswith('.*'):
# In the case of prefix matching we want to ignore local segment.
prospective = Version(prospective.public)
# Split the spec out by dots, and pretend that there is an implicit
@ -450,7 +451,7 @@ class Specifier(_IndividualSpecifier):
# Pad out our two sides with zeros so that they both equal the same
# length.
padded_spec, padded_prospective = _pad_version(
split_spec, shortened_prospective
split_spec, shortened_prospective,
)
return padded_prospective == padded_spec
@ -480,7 +481,7 @@ class Specifier(_IndividualSpecifier):
@_require_version_compare
def _compare_greater_than_equal(
self, prospective: ParsedVersion, spec: str
self, prospective: ParsedVersion, spec: str,
) -> bool:
# NB: Local version identifiers are NOT permitted in the version
@ -561,10 +562,10 @@ class Specifier(_IndividualSpecifier):
# operators, and if they are if they are including an explicit
# prerelease.
operator, version = self._spec
if operator in ["==", ">=", "<=", "~=", "==="]:
if operator in ['==', '>=', '<=', '~=', '===']:
# The == specifier can include a trailing .*, if it does we
# want to remove before parsing.
if operator == "==" and version.endswith(".*"):
if operator == '==' and version.endswith('.*'):
version = version[:-2]
# Parse the version, and if it is a pre-release than this
@ -579,12 +580,12 @@ class Specifier(_IndividualSpecifier):
self._prereleases = value
_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
_prefix_regex = re.compile(r'^([0-9]+)((?:a|b|c|rc)[0-9]+)$')
def _version_split(version: str) -> List[str]:
result: List[str] = []
for item in version.split("."):
def _version_split(version: str) -> list[str]:
result: list[str] = []
for item in version.split('.'):
match = _prefix_regex.search(item)
if match:
result.extend(match.groups())
@ -595,11 +596,11 @@ def _version_split(version: str) -> List[str]:
def _is_not_suffix(segment: str) -> bool:
return not any(
segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
segment.startswith(prefix) for prefix in ('dev', 'a', 'b', 'rc', 'post')
)
def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]:
def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]:
left_split, right_split = [], []
# Get the release segment of our versions
@ -607,28 +608,28 @@ def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
# Get the rest of our versions
left_split.append(left[len(left_split[0]) :])
right_split.append(right[len(right_split[0]) :])
left_split.append(left[len(left_split[0]):])
right_split.append(right[len(right_split[0]):])
# Insert our padding
left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
left_split.insert(1, ['0'] * max(0, len(right_split[0]) - len(left_split[0])))
right_split.insert(1, ['0'] * max(0, len(left_split[0]) - len(right_split[0])))
return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
class SpecifierSet(BaseSpecifier):
def __init__(
self, specifiers: str = "", prereleases: Optional[bool] = None
self, specifiers: str = '', prereleases: bool | None = None,
) -> None:
# Split on , to break each individual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace.
split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
split_specifiers = [s.strip() for s in specifiers.split(',') if s.strip()]
# Parsed each individual specifier, attempting first to make it a
# Specifier and falling back to a LegacySpecifier.
parsed: Set[_IndividualSpecifier] = set()
parsed: set[_IndividualSpecifier] = set()
for specifier in split_specifiers:
try:
parsed.add(Specifier(specifier))
@ -644,20 +645,20 @@ class SpecifierSet(BaseSpecifier):
def __repr__(self) -> str:
pre = (
f", prereleases={self.prereleases!r}"
f', prereleases={self.prereleases!r}'
if self._prereleases is not None
else ""
else ''
)
return f"<SpecifierSet({str(self)!r}{pre})>"
return f'<SpecifierSet({str(self)!r}{pre})>'
def __str__(self) -> str:
return ",".join(sorted(str(s) for s in self._specs))
return ','.join(sorted(str(s) for s in self._specs))
def __hash__(self) -> int:
return hash(self._specs)
def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
if isinstance(other, str):
other = SpecifierSet(other)
elif not isinstance(other, SpecifierSet):
@ -674,8 +675,8 @@ class SpecifierSet(BaseSpecifier):
specifier._prereleases = self._prereleases
else:
raise ValueError(
"Cannot combine SpecifierSets with True and False prerelease "
"overrides."
'Cannot combine SpecifierSets with True and False prerelease '
'overrides.',
)
return specifier
@ -695,7 +696,7 @@ class SpecifierSet(BaseSpecifier):
return iter(self._specs)
@property
def prereleases(self) -> Optional[bool]:
def prereleases(self) -> bool | None:
# If we have been given an explicit prerelease modifier, then we'll
# pass that through here.
@ -720,7 +721,7 @@ class SpecifierSet(BaseSpecifier):
return self.contains(item)
def contains(
self, item: UnparsedVersion, prereleases: Optional[bool] = None
self, item: UnparsedVersion, prereleases: bool | None = None,
) -> bool:
# Ensure that our item is a Version or LegacyVersion instance.
@ -749,7 +750,7 @@ class SpecifierSet(BaseSpecifier):
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter(
self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
self, iterable: Iterable[VersionTypeVar], prereleases: bool | None = None,
) -> Iterable[VersionTypeVar]:
# Determine if we're forcing a prerelease or not, if we're not forcing
@ -769,11 +770,11 @@ class SpecifierSet(BaseSpecifier):
# which will filter out any pre-releases, unless there are no final
# releases, and which will filter out LegacyVersion in general.
else:
filtered: List[VersionTypeVar] = []
found_prereleases: List[VersionTypeVar] = []
filtered: list[VersionTypeVar] = []
found_prereleases: list[VersionTypeVar] = []
item: UnparsedVersion
parsed_version: Union[Version, LegacyVersion]
parsed_version: Version | LegacyVersion
for item in iterable:
# Ensure that we some kind of Version class for this item.

View file

@ -1,38 +1,38 @@
# 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 sys
import sysconfig
from importlib.machinery import EXTENSION_SUFFIXES
from typing import (
Dict,
FrozenSet,
Iterable,
Iterator,
List,
Optional,
Sequence,
Tuple,
Union,
cast,
)
from typing import cast
from typing import Dict
from typing import FrozenSet
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
from . import _manylinux, _musllinux
from . import _manylinux
from . import _musllinux
logger = logging.getLogger(__name__)
PythonVersion = Sequence[int]
MacVersion = Tuple[int, int]
INTERPRETER_SHORT_NAMES: Dict[str, str] = {
"python": "py", # Generic.
"cpython": "cp",
"pypy": "pp",
"ironpython": "ip",
"jython": "jy",
INTERPRETER_SHORT_NAMES: dict[str, str] = {
'python': 'py', # Generic.
'cpython': 'cp',
'pypy': 'pp',
'ironpython': 'ip',
'jython': 'jy',
}
@ -47,7 +47,7 @@ class Tag:
is also supported.
"""
__slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
__slots__ = ['_interpreter', '_abi', '_platform', '_hash']
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
self._interpreter = interpreter.lower()
@ -77,23 +77,23 @@ class 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)
(self._hash == other._hash) and # Short-circuit ASAP for perf reasons.
(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}"
return f'{self._interpreter}-{self._abi}-{self._platform}'
def __repr__(self) -> str:
return f"<{self} @ {id(self)}>"
return f'<{self} @ {id(self)}>'
def parse_tag(tag: str) -> FrozenSet[Tag]:
def parse_tag(tag: str) -> frozenset[Tag]:
"""
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
@ -101,25 +101,25 @@ def parse_tag(tag: str) -> FrozenSet[Tag]:
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("."):
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) -> Union[int, str, None]:
def _get_config_var(name: str, warn: bool = False) -> int | str | None:
value = 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
"Config variable '%s' is unset, Python ABI tag may be incorrect", name,
)
return value
def _normalize_string(string: str) -> str:
return string.replace(".", "_").replace("-", "_")
return string.replace('.', '_').replace('-', '_')
def _abi3_applies(python_version: PythonVersion) -> bool:
@ -131,46 +131,46 @@ def _abi3_applies(python_version: PythonVersion) -> bool:
return len(python_version) > 1 and tuple(python_version) >= (3, 2)
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
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])
debug = pymalloc = ucs4 = ""
with_debug = _get_config_var("Py_DEBUG", warn)
has_refcount = hasattr(sys, "gettotalrefcount")
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
has_ext = '_d.pyd' in EXTENSION_SUFFIXES
if with_debug or (with_debug is None and (has_refcount or has_ext)):
debug = "d"
debug = 'd'
if py_version < (3, 8):
with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
with_pymalloc = _get_config_var('WITH_PYMALLOC', warn)
if with_pymalloc or with_pymalloc is None:
pymalloc = "m"
pymalloc = 'm'
if py_version < (3, 3):
unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
unicode_size = _get_config_var('Py_UNICODE_SIZE', warn)
if unicode_size == 4 or (
unicode_size is None and sys.maxunicode == 0x10FFFF
):
ucs4 = "u"
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}")
abis.append(f'cp{version}')
abis.insert(
0,
"cp{version}{debug}{pymalloc}{ucs4}".format(
version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4
'cp{version}{debug}{pymalloc}{ucs4}'.format(
version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4,
),
)
return abis
def cpython_tags(
python_version: Optional[PythonVersion] = None,
abis: Optional[Iterable[str]] = None,
platforms: Optional[Iterable[str]] = None,
python_version: PythonVersion | None = None,
abis: Iterable[str] | None = None,
platforms: Iterable[str] | None = None,
*,
warn: bool = False,
) -> Iterator[Tag]:
@ -192,7 +192,7 @@ def cpython_tags(
if not python_version:
python_version = sys.version_info[:2]
interpreter = f"cp{_version_nodot(python_version[:2])}"
interpreter = f'cp{_version_nodot(python_version[:2])}'
if abis is None:
if len(python_version) > 1:
@ -201,7 +201,7 @@ def cpython_tags(
abis = []
abis = list(abis)
# 'abi3' and 'none' are explicitly handled later.
for explicit_abi in ("abi3", "none"):
for explicit_abi in ('abi3', 'none'):
try:
abis.remove(explicit_abi)
except ValueError:
@ -212,28 +212,28 @@ def cpython_tags(
for platform_ in platforms:
yield Tag(interpreter, abi, platform_)
if _abi3_applies(python_version):
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
yield from (Tag(interpreter, 'abi3', platform_) for platform_ in platforms)
yield from (Tag(interpreter, 'none', platform_) for platform_ in platforms)
if _abi3_applies(python_version):
for minor_version in range(python_version[1] - 1, 1, -1):
for platform_ in platforms:
interpreter = "cp{version}".format(
version=_version_nodot((python_version[0], minor_version))
interpreter = 'cp{version}'.format(
version=_version_nodot((python_version[0], minor_version)),
)
yield Tag(interpreter, "abi3", platform_)
yield Tag(interpreter, 'abi3', platform_)
def _generic_abi() -> Iterator[str]:
abi = sysconfig.get_config_var("SOABI")
abi = sysconfig.get_config_var('SOABI')
if abi:
yield _normalize_string(abi)
def generic_tags(
interpreter: Optional[str] = None,
abis: Optional[Iterable[str]] = None,
platforms: Optional[Iterable[str]] = None,
interpreter: str | None = None,
abis: Iterable[str] | None = None,
platforms: Iterable[str] | None = None,
*,
warn: bool = False,
) -> Iterator[Tag]:
@ -248,13 +248,13 @@ def generic_tags(
if not interpreter:
interp_name = interpreter_name()
interp_version = interpreter_version(warn=warn)
interpreter = "".join([interp_name, interp_version])
interpreter = ''.join([interp_name, interp_version])
if abis is None:
abis = _generic_abi()
platforms = list(platforms or platform_tags())
abis = list(abis)
if "none" not in abis:
abis.append("none")
if 'none' not in abis:
abis.append('none')
for abi in abis:
for platform_ in platforms:
yield Tag(interpreter, abi, platform_)
@ -268,17 +268,17 @@ def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
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]}"
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))}"
yield f'py{_version_nodot((py_version[0], minor))}'
def compatible_tags(
python_version: Optional[PythonVersion] = None,
interpreter: Optional[str] = None,
platforms: Optional[Iterable[str]] = None,
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.
@ -293,57 +293,57 @@ def compatible_tags(
platforms = list(platforms or platform_tags())
for version in _py_interpreter_range(python_version):
for platform_ in platforms:
yield Tag(version, "none", platform_)
yield Tag(version, 'none', platform_)
if interpreter:
yield Tag(interpreter, "none", "any")
yield Tag(interpreter, 'none', 'any')
for version in _py_interpreter_range(python_version):
yield Tag(version, "none", "any")
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"
if arch.startswith('ppc'):
return 'ppc'
return "i386"
return 'i386'
def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]:
def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> list[str]:
formats = [cpu_arch]
if cpu_arch == "x86_64":
if cpu_arch == 'x86_64':
if version < (10, 4):
return []
formats.extend(["intel", "fat64", "fat32"])
formats.extend(['intel', 'fat64', 'fat32'])
elif cpu_arch == "i386":
elif cpu_arch == 'i386':
if version < (10, 4):
return []
formats.extend(["intel", "fat32", "fat"])
formats.extend(['intel', 'fat32', 'fat'])
elif cpu_arch == "ppc64":
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")
formats.append('fat64')
elif cpu_arch == "ppc":
elif cpu_arch == 'ppc':
if version > (10, 6):
return []
formats.extend(["fat32", "fat"])
formats.extend(['fat32', 'fat'])
if cpu_arch in {"arm64", "x86_64"}:
formats.append("universal2")
if cpu_arch in {'arm64', 'x86_64'}:
formats.append('universal2')
if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
formats.append("universal")
if cpu_arch in {'x86_64', 'i386', 'ppc64', 'ppc', 'intel'}:
formats.append('universal')
return formats
def mac_platforms(
version: Optional[MacVersion] = None, arch: Optional[str] = None
version: MacVersion | None = None, arch: str | None = None,
) -> Iterator[str]:
"""
Yields the platform tags for a macOS system.
@ -355,7 +355,7 @@ def mac_platforms(
"""
version_str, _, cpu_arch = platform.mac_ver()
if version is None:
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
version = cast('MacVersion', tuple(map(int, version_str.split('.')[:2])))
else:
version = version
if arch is None:
@ -370,8 +370,8 @@ def mac_platforms(
compat_version = 10, minor_version
binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format(
major=10, minor=minor_version, binary_format=binary_format
yield 'macosx_{major}_{minor}_{binary_format}'.format(
major=10, minor=minor_version, binary_format=binary_format,
)
if version >= (11, 0):
@ -381,8 +381,8 @@ def mac_platforms(
compat_version = major_version, 0
binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format(
major=major_version, minor=0, binary_format=binary_format
yield 'macosx_{major}_{minor}_{binary_format}'.format(
major=major_version, minor=0, binary_format=binary_format,
)
if version >= (11, 0):
@ -393,12 +393,12 @@ def mac_platforms(
# 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.
if arch == "x86_64":
if arch == 'x86_64':
for minor_version in range(16, 3, -1):
compat_version = 10, minor_version
binary_formats = _mac_binary_formats(compat_version, arch)
for binary_format in binary_formats:
yield "macosx_{major}_{minor}_{binary_format}".format(
yield 'macosx_{major}_{minor}_{binary_format}'.format(
major=compat_version[0],
minor=compat_version[1],
binary_format=binary_format,
@ -406,8 +406,8 @@ def mac_platforms(
else:
for minor_version in range(16, 3, -1):
compat_version = 10, minor_version
binary_format = "universal2"
yield "macosx_{major}_{minor}_{binary_format}".format(
binary_format = 'universal2'
yield 'macosx_{major}_{minor}_{binary_format}'.format(
major=compat_version[0],
minor=compat_version[1],
binary_format=binary_format,
@ -417,11 +417,11 @@ def mac_platforms(
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
linux = _normalize_string(sysconfig.get_platform())
if is_32bit:
if linux == "linux_x86_64":
linux = "linux_i686"
elif linux == "linux_aarch64":
linux = "linux_armv7l"
_, arch = linux.split("_", 1)
if linux == 'linux_x86_64':
linux = 'linux_i686'
elif linux == 'linux_aarch64':
linux = 'linux_armv7l'
_, arch = linux.split('_', 1)
yield from _manylinux.platform_tags(linux, arch)
yield from _musllinux.platform_tags(arch)
yield linux
@ -435,9 +435,9 @@ def platform_tags() -> Iterator[str]:
"""
Provides the platform tags for this installation.
"""
if platform.system() == "Darwin":
if platform.system() == 'Darwin':
return mac_platforms()
elif platform.system() == "Linux":
elif platform.system() == 'Linux':
return _linux_platforms()
else:
return _generic_platforms()
@ -455,7 +455,7 @@ def interpreter_version(*, warn: bool = False) -> str:
"""
Returns the version of the running interpreter.
"""
version = _get_config_var("py_version_nodot", warn=warn)
version = _get_config_var('py_version_nodot', warn=warn)
if version:
version = str(version)
else:
@ -464,7 +464,7 @@ def interpreter_version(*, warn: bool = False) -> str:
def _version_nodot(version: PythonVersion) -> str:
return "".join(map(str, version))
return ''.join(map(str, version))
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
@ -476,12 +476,12 @@ def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
"""
interp_name = interpreter_name()
if interp_name == "cp":
if interp_name == 'cp':
yield from cpython_tags(warn=warn)
else:
yield from generic_tags()
if interp_name == "pp":
yield from compatible_tags(interpreter="pp3")
if interp_name == 'pp':
yield from compatible_tags(interpreter='pp3')
else:
yield from compatible_tags()

View file

@ -1,15 +1,22 @@
# 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 FrozenSet, NewType, Tuple, Union, cast
from typing import cast
from typing import FrozenSet
from typing import NewType
from typing import Tuple
from typing import Union
from .tags import Tag, parse_tag
from .version import InvalidVersion, Version
from .tags import parse_tag
from .tags import Tag
from .version import InvalidVersion
from .version import Version
BuildTag = Union[Tuple[()], Tuple[int, str]]
NormalizedName = NewType("NormalizedName", str)
NormalizedName = NewType('NormalizedName', str)
class InvalidWheelFilename(ValueError):
@ -24,18 +31,18 @@ class InvalidSdistFilename(ValueError):
"""
_canonicalize_regex = re.compile(r"[-_.]+")
_canonicalize_regex = re.compile(r'[-_.]+')
# PEP 427: The build number must start with a digit.
_build_tag_regex = re.compile(r"(\d+)(.*)")
_build_tag_regex = re.compile(r'(\d+)(.*)')
def canonicalize_name(name: str) -> NormalizedName:
# This is taken from PEP 503.
value = _canonicalize_regex.sub("-", name).lower()
value = _canonicalize_regex.sub('-', name).lower()
return cast(NormalizedName, value)
def canonicalize_version(version: Union[Version, str]) -> str:
def canonicalize_version(version: Version | str) -> str:
"""
This is very similar to Version.__str__, but has one subtle difference
with the way it handles the release segment.
@ -53,51 +60,51 @@ def canonicalize_version(version: Union[Version, str]) -> str:
# Epoch
if parsed.epoch != 0:
parts.append(f"{parsed.epoch}!")
parts.append(f'{parsed.epoch}!')
# Release segment
# NB: This strips trailing '.0's to normalize
parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release)))
parts.append(re.sub(r'(\.0)+$', '', '.'.join(str(x) for x in parsed.release)))
# Pre-release
if parsed.pre is not None:
parts.append("".join(str(x) for x in parsed.pre))
parts.append(''.join(str(x) for x in parsed.pre))
# Post-release
if parsed.post is not None:
parts.append(f".post{parsed.post}")
parts.append(f'.post{parsed.post}')
# Development release
if parsed.dev is not None:
parts.append(f".dev{parsed.dev}")
parts.append(f'.dev{parsed.dev}')
# Local version segment
if parsed.local is not None:
parts.append(f"+{parsed.local}")
parts.append(f'+{parsed.local}')
return "".join(parts)
return ''.join(parts)
def parse_wheel_filename(
filename: str,
) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]:
if not filename.endswith(".whl"):
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
if not filename.endswith('.whl'):
raise InvalidWheelFilename(
f"Invalid wheel filename (extension must be '.whl'): {filename}"
f"Invalid wheel filename (extension must be '.whl'): {filename}",
)
filename = filename[:-4]
dashes = filename.count("-")
dashes = filename.count('-')
if dashes not in (4, 5):
raise InvalidWheelFilename(
f"Invalid wheel filename (wrong number of parts): {filename}"
f'Invalid wheel filename (wrong number of parts): {filename}',
)
parts = filename.split("-", dashes - 2)
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}")
if '__' in name_part or re.match(r'^[\w\d._]*$', name_part, re.UNICODE) is None:
raise InvalidWheelFilename(f'Invalid project name: {filename}')
name = canonicalize_name(name_part)
version = Version(parts[1])
if dashes == 5:
@ -105,7 +112,7 @@ def parse_wheel_filename(
build_match = _build_tag_regex.match(build_part)
if build_match is None:
raise InvalidWheelFilename(
f"Invalid build number: {build_part} in '{filename}'"
f"Invalid build number: {build_part} in '{filename}'",
)
build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
else:
@ -114,22 +121,22 @@ def parse_wheel_filename(
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")]
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}"
f' {filename}',
)
# 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("-")
name_part, sep, version_part = file_stem.rpartition('-')
if not sep:
raise InvalidSdistFilename(f"Invalid sdist filename: {filename}")
raise InvalidSdistFilename(f'Invalid sdist filename: {filename}')
name = canonicalize_name(name_part)
version = Version(version_part)

View file

@ -1,16 +1,26 @@
# 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 collections
import itertools
import re
import warnings
from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
from typing import Callable
from typing import Iterator
from typing import List
from typing import Optional
from typing import SupportsInt
from typing import Tuple
from typing import Union
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
from ._structures import Infinity
from ._structures import InfinityType
from ._structures import NegativeInfinity
from ._structures import NegativeInfinityType
__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
__all__ = ['parse', 'Version', 'LegacyVersion', 'InvalidVersion', 'VERSION_PATTERN']
InfiniteTypes = Union[InfinityType, NegativeInfinityType]
PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
@ -27,19 +37,19 @@ LocalType = Union[
],
]
CmpKey = Tuple[
int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType,
]
LegacyCmpKey = Tuple[int, Tuple[str, ...]]
VersionComparisonMethod = Callable[
[Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
[Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool,
]
_Version = collections.namedtuple(
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
'_Version', ['epoch', 'release', 'dev', 'pre', 'post', 'local'],
)
def parse(version: str) -> Union["LegacyVersion", "Version"]:
def parse(version: str) -> LegacyVersion | Version:
"""
Parse the given version string and return either a :class:`Version` object
or a :class:`LegacyVersion` object depending on if the given version is
@ -58,7 +68,7 @@ class InvalidVersion(ValueError):
class _BaseVersion:
_key: Union[CmpKey, LegacyCmpKey]
_key: CmpKey | LegacyCmpKey
def __hash__(self) -> int:
return hash(self._key)
@ -66,13 +76,13 @@ class _BaseVersion:
# 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:
def __lt__(self, other: _BaseVersion) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key < other._key
def __le__(self, other: "_BaseVersion") -> bool:
def __le__(self, other: _BaseVersion) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
@ -84,13 +94,13 @@ class _BaseVersion:
return self._key == other._key
def __ge__(self, other: "_BaseVersion") -> bool:
def __ge__(self, other: _BaseVersion) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
return self._key >= other._key
def __gt__(self, other: "_BaseVersion") -> bool:
def __gt__(self, other: _BaseVersion) -> bool:
if not isinstance(other, _BaseVersion):
return NotImplemented
@ -109,8 +119,8 @@ class LegacyVersion(_BaseVersion):
self._key = _legacy_cmpkey(self._version)
warnings.warn(
"Creating a LegacyVersion has been deprecated and will be "
"removed in the next major release",
'Creating a LegacyVersion has been deprecated and will be '
'removed in the next major release',
DeprecationWarning,
)
@ -165,14 +175,14 @@ class LegacyVersion(_BaseVersion):
return False
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
_legacy_version_component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
_legacy_version_replacement_map = {
"pre": "c",
"preview": "c",
"-": "final-",
"rc": "c",
"dev": "@",
'pre': 'c',
'preview': 'c',
'-': 'final-',
'rc': 'c',
'dev': '@',
}
@ -180,17 +190,17 @@ def _parse_version_parts(s: str) -> Iterator[str]:
for part in _legacy_version_component_re.split(s):
part = _legacy_version_replacement_map.get(part, part)
if not part or part == ".":
if not part or part == '.':
continue
if part[:1] in "0123456789":
if part[:1] in '0123456789':
# pad for numeric comparison
yield part.zfill(8)
else:
yield "*" + part
yield '*' + part
# ensure that alpha/beta/candidate are before final
yield "*final"
yield '*final'
def _legacy_cmpkey(version: str) -> LegacyCmpKey:
@ -203,16 +213,16 @@ def _legacy_cmpkey(version: str) -> LegacyCmpKey:
# This scheme is taken from pkg_resources.parse_version setuptools prior to
# it's adoption of the packaging library.
parts: List[str] = []
parts: list[str] = []
for part in _parse_version_parts(version.lower()):
if part.startswith("*"):
if part.startswith('*'):
# remove "-" before a prerelease tag
if part < "*final":
while parts and parts[-1] == "*final-":
if part < '*final':
while parts and parts[-1] == '*final-':
parts.pop()
# remove trailing zeros from each series of numeric parts
while parts and parts[-1] == "00000000":
while parts and parts[-1] == '00000000':
parts.pop()
parts.append(part)
@ -256,7 +266,7 @@ VERSION_PATTERN = r"""
class Version(_BaseVersion):
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
_regex = re.compile(r'^\s*' + VERSION_PATTERN + r'\s*$', re.VERBOSE | re.IGNORECASE)
def __init__(self, version: str) -> None:
@ -267,14 +277,14 @@ class Version(_BaseVersion):
# Store the parsed out pieces of the version
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
epoch=int(match.group('epoch')) if match.group('epoch') else 0,
release=tuple(int(i) for i in match.group('release').split('.')),
pre=_parse_letter_version(match.group('pre_l'), match.group('pre_n')),
post=_parse_letter_version(
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
match.group('post_l'), match.group('post_n1') or match.group('post_n2'),
),
dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")),
dev=_parse_letter_version(match.group('dev_l'), match.group('dev_n')),
local=_parse_local_version(match.group('local')),
)
# Generate a key which will be used for sorting
@ -295,28 +305,28 @@ class Version(_BaseVersion):
# Epoch
if self.epoch != 0:
parts.append(f"{self.epoch}!")
parts.append(f'{self.epoch}!')
# Release segment
parts.append(".".join(str(x) for x in self.release))
parts.append('.'.join(str(x) for x in self.release))
# Pre-release
if self.pre is not None:
parts.append("".join(str(x) for x in self.pre))
parts.append(''.join(str(x) for x in self.pre))
# Post-release
if self.post is not None:
parts.append(f".post{self.post}")
parts.append(f'.post{self.post}')
# Development release
if self.dev is not None:
parts.append(f".dev{self.dev}")
parts.append(f'.dev{self.dev}')
# Local version segment
if self.local is not None:
parts.append(f"+{self.local}")
parts.append(f'+{self.local}')
return "".join(parts)
return ''.join(parts)
@property
def epoch(self) -> int:
@ -324,33 +334,33 @@ class Version(_BaseVersion):
return _epoch
@property
def release(self) -> Tuple[int, ...]:
_release: Tuple[int, ...] = self._version.release
def release(self) -> tuple[int, ...]:
_release: tuple[int, ...] = self._version.release
return _release
@property
def pre(self) -> Optional[Tuple[str, int]]:
_pre: Optional[Tuple[str, int]] = self._version.pre
def pre(self) -> tuple[str, int] | None:
_pre: tuple[str, int] | None = self._version.pre
return _pre
@property
def post(self) -> Optional[int]:
def post(self) -> int | None:
return self._version.post[1] if self._version.post else None
@property
def dev(self) -> Optional[int]:
def dev(self) -> int | None:
return self._version.dev[1] if self._version.dev else None
@property
def local(self) -> Optional[str]:
def local(self) -> str | None:
if self._version.local:
return ".".join(str(x) for x in self._version.local)
return '.'.join(str(x) for x in self._version.local)
else:
return None
@property
def public(self) -> str:
return str(self).split("+", 1)[0]
return str(self).split('+', 1)[0]
@property
def base_version(self) -> str:
@ -358,12 +368,12 @@ class Version(_BaseVersion):
# Epoch
if self.epoch != 0:
parts.append(f"{self.epoch}!")
parts.append(f'{self.epoch}!')
# Release segment
parts.append(".".join(str(x) for x in self.release))
parts.append('.'.join(str(x) for x in self.release))
return "".join(parts)
return ''.join(parts)
@property
def is_prerelease(self) -> bool:
@ -391,8 +401,8 @@ class Version(_BaseVersion):
def _parse_letter_version(
letter: str, number: Union[str, bytes, SupportsInt]
) -> Optional[Tuple[str, int]]:
letter: str, number: str | bytes | SupportsInt,
) -> tuple[str, int] | None:
if letter:
# We consider there to be an implicit 0 in a pre-release if there is
@ -406,30 +416,30 @@ def _parse_letter_version(
# 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.
if letter == "alpha":
letter = "a"
elif letter == "beta":
letter = "b"
elif letter in ["c", "pre", "preview"]:
letter = "rc"
elif letter in ["rev", "r"]:
letter = "post"
if letter == 'alpha':
letter = 'a'
elif letter == 'beta':
letter = 'b'
elif letter in ['c', 'pre', 'preview']:
letter = 'rc'
elif letter in ['rev', 'r']:
letter = 'post'
return letter, int(number)
if not letter and 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)
letter = "post"
letter = 'post'
return letter, int(number)
return None
_local_version_separators = re.compile(r"[\._-]")
_local_version_separators = re.compile(r'[\._-]')
def _parse_local_version(local: str) -> Optional[LocalType]:
def _parse_local_version(local: str) -> LocalType | None:
"""
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
"""
@ -443,11 +453,11 @@ def _parse_local_version(local: str) -> Optional[LocalType]:
def _cmpkey(
epoch: int,
release: Tuple[int, ...],
pre: Optional[Tuple[str, int]],
post: Optional[Tuple[str, int]],
dev: Optional[Tuple[str, int]],
local: Optional[Tuple[SubLocalType]],
release: tuple[int, ...],
pre: tuple[str, int] | None,
post: tuple[str, int] | None,
dev: tuple[str, int] | None,
local: tuple[SubLocalType] | None,
) -> CmpKey:
# When we compare a release version, we want to compare it with all of the
@ -456,7 +466,7 @@ def _cmpkey(
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
_release = tuple(
reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))),
)
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
@ -498,7 +508,7 @@ def _cmpkey(
# - 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
(i, '') if isinstance(i, int) else (NegativeInfinity, i) for i in local
)
return epoch, _release, _pre, _post, _dev, _local