Bump packaging from 24.0 to 24.1 (#2347)

* Bump packaging from 24.0 to 24.1

Bumps [packaging](https://github.com/pypa/packaging) from 24.0 to 24.1.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/24.0...24.1)

---
updated-dependencies:
- dependency-name: packaging
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update packaging==24.1

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2024-06-19 00:04:34 -07:00 committed by GitHub
parent 28ad2716ba
commit 2f1607b96b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 288 additions and 238 deletions

View file

@ -6,7 +6,7 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages" __summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging" __uri__ = "https://github.com/pypa/packaging"
__version__ = "24.0" __version__ = "24.1"
__author__ = "Donald Stufft and individual contributors" __author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io" __email__ = "donald@stufft.io"

View file

@ -8,10 +8,12 @@ Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
""" """
from __future__ import annotations
import enum import enum
import os import os
import struct import struct
from typing import IO, Optional, Tuple from typing import IO
class ELFInvalid(ValueError): class ELFInvalid(ValueError):
@ -87,11 +89,11 @@ class ELFFile:
except struct.error as e: except struct.error as e:
raise ELFInvalid("unable to parse machine and section information") from e raise ELFInvalid("unable to parse machine and section information") from e
def _read(self, fmt: str) -> Tuple[int, ...]: def _read(self, fmt: str) -> tuple[int, ...]:
return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
@property @property
def interpreter(self) -> Optional[str]: def interpreter(self) -> str | None:
""" """
The path recorded in the ``PT_INTERP`` section header. The path recorded in the ``PT_INTERP`` section header.
""" """

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import collections import collections
import contextlib import contextlib
import functools import functools
@ -5,7 +7,7 @@ import os
import re import re
import sys import sys
import warnings import warnings
from typing import Dict, Generator, Iterator, NamedTuple, Optional, Sequence, Tuple from typing import Generator, Iterator, NamedTuple, Sequence
from ._elffile import EIClass, EIData, ELFFile, EMachine from ._elffile import EIClass, EIData, ELFFile, EMachine
@ -17,7 +19,7 @@ EF_ARM_ABI_FLOAT_HARD = 0x00000400
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str` # `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
# as the type for `path` until then. # as the type for `path` until then.
@contextlib.contextmanager @contextlib.contextmanager
def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]: def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
try: try:
with open(path, "rb") as f: with open(path, "rb") as f:
yield ELFFile(f) yield ELFFile(f)
@ -72,7 +74,7 @@ def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
# For now, guess what the highest minor version might be, assume it will # For now, guess what the highest minor version might be, assume it will
# be 50 for testing. Once this actually happens, update the dictionary # be 50 for testing. Once this actually happens, update the dictionary
# with the actual value. # 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): class _GLibCVersion(NamedTuple):
@ -80,7 +82,7 @@ class _GLibCVersion(NamedTuple):
minor: int 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. Primary implementation of glibc_version_string using os.confstr.
""" """
@ -90,7 +92,7 @@ def _glibc_version_string_confstr() -> Optional[str]:
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
try: try:
# Should be a string like "glibc 2.17". # Should be a string like "glibc 2.17".
version_string: Optional[str] = os.confstr("CS_GNU_LIBC_VERSION") version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION")
assert version_string is not None assert version_string is not None
_, version = version_string.rsplit() _, version = version_string.rsplit()
except (AssertionError, AttributeError, OSError, ValueError): except (AssertionError, AttributeError, OSError, ValueError):
@ -99,7 +101,7 @@ def _glibc_version_string_confstr() -> Optional[str]:
return version return version
def _glibc_version_string_ctypes() -> Optional[str]: def _glibc_version_string_ctypes() -> str | None:
""" """
Fallback implementation of glibc_version_string using ctypes. Fallback implementation of glibc_version_string using ctypes.
""" """
@ -143,12 +145,12 @@ def _glibc_version_string_ctypes() -> Optional[str]:
return version_str 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.""" """Returns glibc version string, or None if not using glibc."""
return _glibc_version_string_confstr() or _glibc_version_string_ctypes() 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. """Parse glibc version.
We use a regexp instead of str.split because we want to discard any We use a regexp instead of str.split because we want to discard any
@ -167,8 +169,8 @@ def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
return int(m.group("major")), int(m.group("minor")) return int(m.group("major")), int(m.group("minor"))
@functools.lru_cache() @functools.lru_cache
def _get_glibc_version() -> Tuple[int, int]: def _get_glibc_version() -> tuple[int, int]:
version_str = _glibc_version_string() version_str = _glibc_version_string()
if version_str is None: if version_str is None:
return (-1, -1) return (-1, -1)

View file

@ -4,11 +4,13 @@ This module implements logic to detect if the currently running Python is
linked against musl, and what musl version is used. linked against musl, and what musl version is used.
""" """
from __future__ import annotations
import functools import functools
import re import re
import subprocess import subprocess
import sys import sys
from typing import Iterator, NamedTuple, Optional, Sequence from typing import Iterator, NamedTuple, Sequence
from ._elffile import ELFFile from ._elffile import ELFFile
@ -18,7 +20,7 @@ class _MuslVersion(NamedTuple):
minor: int 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] 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 return None
@ -28,8 +30,8 @@ def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
@functools.lru_cache() @functools.lru_cache
def _get_musl_version(executable: str) -> Optional[_MuslVersion]: def _get_musl_version(executable: str) -> _MuslVersion | None:
"""Detect currently-running musl runtime version. """Detect currently-running musl runtime version.
This is done by checking the specified executable's dynamic linking This is done by checking the specified executable's dynamic linking

View file

@ -1,11 +1,13 @@
"""Handwritten parser of dependency specifiers. """Handwritten parser of dependency specifiers.
The docstring for each __parse_* function contains ENBF-inspired grammar representing The docstring for each __parse_* function contains EBNF-inspired grammar representing
the implementation. the implementation.
""" """
from __future__ import annotations
import ast import ast
from typing import Any, List, NamedTuple, Optional, Tuple, Union from typing import NamedTuple, Sequence, Tuple, Union
from ._tokenizer import DEFAULT_RULES, Tokenizer from ._tokenizer import DEFAULT_RULES, Tokenizer
@ -41,20 +43,16 @@ class Op(Node):
MarkerVar = Union[Variable, Value] MarkerVar = Union[Variable, Value]
MarkerItem = Tuple[MarkerVar, Op, MarkerVar] MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
# MarkerAtom = Union[MarkerItem, List["MarkerAtom"]] MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
# MarkerList = List[Union["MarkerList", MarkerAtom, str]] MarkerList = Sequence[Union["MarkerList", MarkerAtom, str]]
# mypy does not support recursive type definition
# https://github.com/python/mypy/issues/731
MarkerAtom = Any
MarkerList = List[Any]
class ParsedRequirement(NamedTuple): class ParsedRequirement(NamedTuple):
name: str name: str
url: str url: str
extras: List[str] extras: list[str]
specifier: str specifier: str
marker: Optional[MarkerList] marker: MarkerList | None
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
@ -87,7 +85,7 @@ def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
def _parse_requirement_details( def _parse_requirement_details(
tokenizer: Tokenizer, tokenizer: Tokenizer,
) -> Tuple[str, str, Optional[MarkerList]]: ) -> tuple[str, str, MarkerList | None]:
""" """
requirement_details = AT URL (WS requirement_marker?)? requirement_details = AT URL (WS requirement_marker?)?
| specifier WS? (requirement_marker)? | specifier WS? (requirement_marker)?
@ -156,7 +154,7 @@ def _parse_requirement_marker(
return marker return marker
def _parse_extras(tokenizer: Tokenizer) -> List[str]: def _parse_extras(tokenizer: Tokenizer) -> list[str]:
""" """
extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)? extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
""" """
@ -175,11 +173,11 @@ def _parse_extras(tokenizer: Tokenizer) -> List[str]:
return extras return extras
def _parse_extras_list(tokenizer: Tokenizer) -> List[str]: def _parse_extras_list(tokenizer: Tokenizer) -> list[str]:
""" """
extras_list = identifier (wsp* ',' wsp* identifier)* extras_list = identifier (wsp* ',' wsp* identifier)*
""" """
extras: List[str] = [] extras: list[str] = []
if not tokenizer.check("IDENTIFIER"): if not tokenizer.check("IDENTIFIER"):
return extras return extras

View file

@ -1,7 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union from typing import Iterator, NoReturn
from .specifiers import Specifier from .specifiers import Specifier
@ -21,7 +23,7 @@ class ParserSyntaxError(Exception):
message: str, message: str,
*, *,
source: str, source: str,
span: Tuple[int, int], span: tuple[int, int],
) -> None: ) -> None:
self.span = span self.span = span
self.message = message self.message = message
@ -34,7 +36,7 @@ class ParserSyntaxError(Exception):
return "\n ".join([self.message, self.source, marker]) return "\n ".join([self.message, self.source, marker])
DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = { DEFAULT_RULES: dict[str, str | re.Pattern[str]] = {
"LEFT_PARENTHESIS": r"\(", "LEFT_PARENTHESIS": r"\(",
"RIGHT_PARENTHESIS": r"\)", "RIGHT_PARENTHESIS": r"\)",
"LEFT_BRACKET": r"\[", "LEFT_BRACKET": r"\[",
@ -96,13 +98,13 @@ class Tokenizer:
self, self,
source: str, source: str,
*, *,
rules: "Dict[str, Union[str, re.Pattern[str]]]", rules: dict[str, str | re.Pattern[str]],
) -> None: ) -> None:
self.source = source self.source = source
self.rules: Dict[str, re.Pattern[str]] = { self.rules: dict[str, re.Pattern[str]] = {
name: re.compile(pattern) for name, pattern in rules.items() name: re.compile(pattern) for name, pattern in rules.items()
} }
self.next_token: Optional[Token] = None self.next_token: Token | None = None
self.position = 0 self.position = 0
def consume(self, name: str) -> None: def consume(self, name: str) -> None:
@ -154,8 +156,8 @@ class Tokenizer:
self, self,
message: str, message: str,
*, *,
span_start: Optional[int] = None, span_start: int | None = None,
span_end: Optional[int] = None, span_end: int | None = None,
) -> NoReturn: ) -> NoReturn:
"""Raise ParserSyntaxError at the given position.""" """Raise ParserSyntaxError at the given position."""
span = ( span = (

View file

@ -2,20 +2,16 @@
# 2.0, and the BSD License. See the LICENSE file in the root of this repository # 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details. # for complete details.
from __future__ import annotations
import operator import operator
import os import os
import platform import platform
import sys import sys
from typing import Any, Callable, Dict, List, Optional, Tuple, Union from typing import Any, Callable, TypedDict, cast
from ._parser import ( from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
MarkerAtom, from ._parser import parse_marker as _parse_marker
MarkerList,
Op,
Value,
Variable,
parse_marker as _parse_marker,
)
from ._tokenizer import ParserSyntaxError from ._tokenizer import ParserSyntaxError
from .specifiers import InvalidSpecifier, Specifier from .specifiers import InvalidSpecifier, Specifier
from .utils import canonicalize_name from .utils import canonicalize_name
@ -50,6 +46,78 @@ class UndefinedEnvironmentName(ValueError):
""" """
class Environment(TypedDict):
implementation_name: str
"""The implementation's identifier, e.g. ``'cpython'``."""
implementation_version: str
"""
The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or
``'7.3.13'`` for PyPy3.10 v7.3.13.
"""
os_name: str
"""
The value of :py:data:`os.name`. The name of the operating system dependent module
imported, e.g. ``'posix'``.
"""
platform_machine: str
"""
Returns the machine type, e.g. ``'i386'``.
An empty string if the value cannot be determined.
"""
platform_release: str
"""
The system's release, e.g. ``'2.2.0'`` or ``'NT'``.
An empty string if the value cannot be determined.
"""
platform_system: str
"""
The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``.
An empty string if the value cannot be determined.
"""
platform_version: str
"""
The system's release version, e.g. ``'#3 on degas'``.
An empty string if the value cannot be determined.
"""
python_full_version: str
"""
The Python version as string ``'major.minor.patchlevel'``.
Note that unlike the Python :py:data:`sys.version`, this value will always include
the patchlevel (it defaults to 0).
"""
platform_python_implementation: str
"""
A string identifying the Python implementation, e.g. ``'CPython'``.
"""
python_version: str
"""The Python version as string ``'major.minor'``."""
sys_platform: str
"""
This string contains a platform identifier that can be used to append
platform-specific components to :py:data:`sys.path`, for instance.
For Unix systems, except on Linux and AIX, this is the lowercased OS name as
returned by ``uname -s`` with the first part of the version as returned by
``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python
was built.
"""
def _normalize_extra_values(results: Any) -> Any: def _normalize_extra_values(results: Any) -> Any:
""" """
Normalize extra values. Normalize extra values.
@ -67,9 +135,8 @@ def _normalize_extra_values(results: Any) -> Any:
def _format_marker( def _format_marker(
marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True marker: list[str] | MarkerAtom | str, first: bool | None = True
) -> str: ) -> str:
assert isinstance(marker, (list, tuple, str)) assert isinstance(marker, (list, tuple, str))
# Sometimes we have a structure like [[...]] which is a single item list # Sometimes we have a structure like [[...]] which is a single item list
@ -95,7 +162,7 @@ def _format_marker(
return marker return marker
_operators: Dict[str, Operator] = { _operators: dict[str, Operator] = {
"in": lambda lhs, rhs: lhs in rhs, "in": lambda lhs, rhs: lhs in rhs,
"not in": lambda lhs, rhs: lhs not in rhs, "not in": lambda lhs, rhs: lhs not in rhs,
"<": operator.lt, "<": operator.lt,
@ -115,14 +182,14 @@ def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
else: else:
return spec.contains(lhs, prereleases=True) return spec.contains(lhs, prereleases=True)
oper: Optional[Operator] = _operators.get(op.serialize()) oper: Operator | None = _operators.get(op.serialize())
if oper is None: 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) return oper(lhs, rhs)
def _normalize(*values: str, key: str) -> Tuple[str, ...]: def _normalize(*values: str, key: str) -> tuple[str, ...]:
# PEP 685 Comparison of extra names for optional distribution dependencies # PEP 685 Comparison of extra names for optional distribution dependencies
# https://peps.python.org/pep-0685/ # https://peps.python.org/pep-0685/
# > When comparing extra names, tools MUST normalize the names being # > When comparing extra names, tools MUST normalize the names being
@ -134,8 +201,8 @@ def _normalize(*values: str, key: str) -> Tuple[str, ...]:
return values return values
def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool: def _evaluate_markers(markers: MarkerList, environment: dict[str, str]) -> bool:
groups: List[List[bool]] = [[]] groups: list[list[bool]] = [[]]
for marker in markers: for marker in markers:
assert isinstance(marker, (list, tuple, str)) assert isinstance(marker, (list, tuple, str))
@ -164,7 +231,7 @@ def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool:
return any(all(item) for item in groups) return any(all(item) for item in groups)
def format_full_version(info: "sys._version_info") -> str: def format_full_version(info: sys._version_info) -> str:
version = "{0.major}.{0.minor}.{0.micro}".format(info) version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel kind = info.releaselevel
if kind != "final": if kind != "final":
@ -172,7 +239,7 @@ def format_full_version(info: "sys._version_info") -> str:
return version return version
def default_environment() -> Dict[str, str]: def default_environment() -> Environment:
iver = format_full_version(sys.implementation.version) iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name implementation_name = sys.implementation.name
return { return {
@ -231,7 +298,7 @@ class Marker:
return str(self) == str(other) return str(self) == str(other)
def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: def evaluate(self, environment: dict[str, str] | None = None) -> bool:
"""Evaluate a marker. """Evaluate a marker.
Return the boolean from evaluating the given marker against the Return the boolean from evaluating the given marker against the
@ -240,8 +307,14 @@ class Marker:
The environment is determined from the current Python process. The environment is determined from the current Python process.
""" """
current_environment = default_environment() current_environment = cast("dict[str, str]", default_environment())
current_environment["extra"] = "" current_environment["extra"] = ""
# Work around platform.python_version() returning something that is not PEP 440
# compliant for non-tagged Python builds. We preserve default_environment()'s
# behavior of returning platform.python_version() verbatim, and leave it to the
# caller to provide a syntactically valid version if they want to override it.
if current_environment["python_full_version"].endswith("+"):
current_environment["python_full_version"] += "local"
if environment is not None: if environment is not None:
current_environment.update(environment) current_environment.update(environment)
# The API used to allow setting extra to None. We need to handle this # The API used to allow setting extra to None. We need to handle this

View file

@ -1,50 +1,31 @@
from __future__ import annotations
import email.feedparser import email.feedparser
import email.header import email.header
import email.message import email.message
import email.parser import email.parser
import email.policy import email.policy
import sys
import typing import typing
from typing import ( from typing import (
Any, Any,
Callable, Callable,
Dict,
Generic, Generic,
List, Literal,
Optional, TypedDict,
Tuple,
Type,
Union,
cast, cast,
) )
from . import requirements, specifiers, utils, version as version_module from . import requirements, specifiers, utils
from . import version as version_module
T = typing.TypeVar("T") T = typing.TypeVar("T")
if sys.version_info[:2] >= (3, 8): # pragma: no cover
from typing import Literal, TypedDict
else: # pragma: no cover
if typing.TYPE_CHECKING:
from typing_extensions import Literal, TypedDict
else:
try:
from typing_extensions import Literal, TypedDict
except ImportError:
class Literal:
def __init_subclass__(*_args, **_kwargs):
pass
class TypedDict:
def __init_subclass__(*_args, **_kwargs):
pass
try: try:
ExceptionGroup ExceptionGroup
except NameError: # pragma: no cover except NameError: # pragma: no cover
class ExceptionGroup(Exception): # noqa: N818 class ExceptionGroup(Exception):
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11. """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
If :external:exc:`ExceptionGroup` is already defined by Python itself, If :external:exc:`ExceptionGroup` is already defined by Python itself,
@ -52,9 +33,9 @@ except NameError: # pragma: no cover
""" """
message: str message: str
exceptions: List[Exception] exceptions: list[Exception]
def __init__(self, message: str, exceptions: List[Exception]) -> None: def __init__(self, message: str, exceptions: list[Exception]) -> None:
self.message = message self.message = message
self.exceptions = exceptions self.exceptions = exceptions
@ -100,32 +81,32 @@ class RawMetadata(TypedDict, total=False):
metadata_version: str metadata_version: str
name: str name: str
version: str version: str
platforms: List[str] platforms: list[str]
summary: str summary: str
description: str description: str
keywords: List[str] keywords: list[str]
home_page: str home_page: str
author: str author: str
author_email: str author_email: str
license: str license: str
# Metadata 1.1 - PEP 314 # Metadata 1.1 - PEP 314
supported_platforms: List[str] supported_platforms: list[str]
download_url: str download_url: str
classifiers: List[str] classifiers: list[str]
requires: List[str] requires: list[str]
provides: List[str] provides: list[str]
obsoletes: List[str] obsoletes: list[str]
# Metadata 1.2 - PEP 345 # Metadata 1.2 - PEP 345
maintainer: str maintainer: str
maintainer_email: str maintainer_email: str
requires_dist: List[str] requires_dist: list[str]
provides_dist: List[str] provides_dist: list[str]
obsoletes_dist: List[str] obsoletes_dist: list[str]
requires_python: str requires_python: str
requires_external: List[str] requires_external: list[str]
project_urls: Dict[str, str] project_urls: dict[str, str]
# Metadata 2.0 # Metadata 2.0
# PEP 426 attempted to completely revamp the metadata format # PEP 426 attempted to completely revamp the metadata format
@ -138,10 +119,10 @@ class RawMetadata(TypedDict, total=False):
# Metadata 2.1 - PEP 566 # Metadata 2.1 - PEP 566
description_content_type: str description_content_type: str
provides_extra: List[str] provides_extra: list[str]
# Metadata 2.2 - PEP 643 # Metadata 2.2 - PEP 643
dynamic: List[str] dynamic: list[str]
# Metadata 2.3 - PEP 685 # Metadata 2.3 - PEP 685
# No new fields were added in PEP 685, just some edge case were # No new fields were added in PEP 685, just some edge case were
@ -185,12 +166,12 @@ _DICT_FIELDS = {
} }
def _parse_keywords(data: str) -> List[str]: def _parse_keywords(data: str) -> list[str]:
"""Split a string of comma-separate keyboards into a list of keywords.""" """Split a string of comma-separate keyboards into a list of keywords."""
return [k.strip() for k in data.split(",")] return [k.strip() for k in data.split(",")]
def _parse_project_urls(data: List[str]) -> Dict[str, str]: def _parse_project_urls(data: list[str]) -> dict[str, str]:
"""Parse a list of label/URL string pairings separated by a comma.""" """Parse a list of label/URL string pairings separated by a comma."""
urls = {} urls = {}
for pair in data: for pair in data:
@ -230,7 +211,7 @@ def _parse_project_urls(data: List[str]) -> Dict[str, str]:
return urls return urls
def _get_payload(msg: email.message.Message, source: Union[bytes, str]) -> str: def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
"""Get the body of the message.""" """Get the body of the message."""
# If our source is a str, then our caller has managed encodings for us, # If our source is a str, then our caller has managed encodings for us,
# and we don't need to deal with it. # and we don't need to deal with it.
@ -292,7 +273,7 @@ _EMAIL_TO_RAW_MAPPING = {
_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()} _RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str, List[str]]]: def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
"""Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``). """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
This function returns a two-item tuple of dicts. The first dict is of This function returns a two-item tuple of dicts. The first dict is of
@ -308,8 +289,8 @@ def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str, List[st
included in this dict. included in this dict.
""" """
raw: Dict[str, Union[str, List[str], Dict[str, str]]] = {} raw: dict[str, str | list[str] | dict[str, str]] = {}
unparsed: Dict[str, List[str]] = {} unparsed: dict[str, list[str]] = {}
if isinstance(data, str): if isinstance(data, str):
parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data) parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
@ -357,7 +338,7 @@ def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str, List[st
# The Header object stores it's data as chunks, and each chunk # The Header object stores it's data as chunks, and each chunk
# can be independently encoded, so we'll need to check each # can be independently encoded, so we'll need to check each
# of them. # of them.
chunks: List[Tuple[bytes, Optional[str]]] = [] chunks: list[tuple[bytes, str | None]] = []
for bin, encoding in email.header.decode_header(h): for bin, encoding in email.header.decode_header(h):
try: try:
bin.decode("utf8", "strict") bin.decode("utf8", "strict")
@ -499,11 +480,11 @@ class _Validator(Generic[T]):
) -> None: ) -> None:
self.added = added self.added = added
def __set_name__(self, _owner: "Metadata", name: str) -> None: def __set_name__(self, _owner: Metadata, name: str) -> None:
self.name = name self.name = name
self.raw_name = _RAW_TO_EMAIL_MAPPING[name] self.raw_name = _RAW_TO_EMAIL_MAPPING[name]
def __get__(self, instance: "Metadata", _owner: Type["Metadata"]) -> T: def __get__(self, instance: Metadata, _owner: type[Metadata]) -> T:
# With Python 3.8, the caching can be replaced with functools.cached_property(). # With Python 3.8, the caching can be replaced with functools.cached_property().
# No need to check the cache as attribute lookup will resolve into the # No need to check the cache as attribute lookup will resolve into the
# instance's __dict__ before __get__ is called. # instance's __dict__ before __get__ is called.
@ -531,7 +512,7 @@ class _Validator(Generic[T]):
return cast(T, value) return cast(T, value)
def _invalid_metadata( def _invalid_metadata(
self, msg: str, cause: Optional[Exception] = None self, msg: str, cause: Exception | None = None
) -> InvalidMetadata: ) -> InvalidMetadata:
exc = InvalidMetadata( exc = InvalidMetadata(
self.raw_name, msg.format_map({"field": repr(self.raw_name)}) self.raw_name, msg.format_map({"field": repr(self.raw_name)})
@ -606,7 +587,7 @@ class _Validator(Generic[T]):
) )
return value return value
def _process_dynamic(self, value: List[str]) -> List[str]: def _process_dynamic(self, value: list[str]) -> list[str]:
for dynamic_field in map(str.lower, value): for dynamic_field in map(str.lower, value):
if dynamic_field in {"name", "version", "metadata-version"}: if dynamic_field in {"name", "version", "metadata-version"}:
raise self._invalid_metadata( raise self._invalid_metadata(
@ -618,8 +599,8 @@ class _Validator(Generic[T]):
def _process_provides_extra( def _process_provides_extra(
self, self,
value: List[str], value: list[str],
) -> List[utils.NormalizedName]: ) -> list[utils.NormalizedName]:
normalized_names = [] normalized_names = []
try: try:
for name in value: for name in value:
@ -641,8 +622,8 @@ class _Validator(Generic[T]):
def _process_requires_dist( def _process_requires_dist(
self, self,
value: List[str], value: list[str],
) -> List[requirements.Requirement]: ) -> list[requirements.Requirement]:
reqs = [] reqs = []
try: try:
for req in value: for req in value:
@ -665,7 +646,7 @@ class Metadata:
_raw: RawMetadata _raw: RawMetadata
@classmethod @classmethod
def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> "Metadata": def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> Metadata:
"""Create an instance from :class:`RawMetadata`. """Create an instance from :class:`RawMetadata`.
If *validate* is true, all metadata will be validated. All exceptions If *validate* is true, all metadata will be validated. All exceptions
@ -675,7 +656,7 @@ class Metadata:
ins._raw = data.copy() # Mutations occur due to caching enriched values. ins._raw = data.copy() # Mutations occur due to caching enriched values.
if validate: if validate:
exceptions: List[Exception] = [] exceptions: list[Exception] = []
try: try:
metadata_version = ins.metadata_version metadata_version = ins.metadata_version
metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version) metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
@ -722,9 +703,7 @@ class Metadata:
return ins return ins
@classmethod @classmethod
def from_email( def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata:
cls, data: Union[bytes, str], *, validate: bool = True
) -> "Metadata":
"""Parse metadata from email headers. """Parse metadata from email headers.
If *validate* is true, the metadata will be validated. All exceptions If *validate* is true, the metadata will be validated. All exceptions
@ -760,66 +739,66 @@ class Metadata:
*validate* parameter)""" *validate* parameter)"""
version: _Validator[version_module.Version] = _Validator() version: _Validator[version_module.Version] = _Validator()
""":external:ref:`core-metadata-version` (required)""" """:external:ref:`core-metadata-version` (required)"""
dynamic: _Validator[Optional[List[str]]] = _Validator( dynamic: _Validator[list[str] | None] = _Validator(
added="2.2", added="2.2",
) )
""":external:ref:`core-metadata-dynamic` """:external:ref:`core-metadata-dynamic`
(validated against core metadata field names and lowercased)""" (validated against core metadata field names and lowercased)"""
platforms: _Validator[Optional[List[str]]] = _Validator() platforms: _Validator[list[str] | None] = _Validator()
""":external:ref:`core-metadata-platform`""" """:external:ref:`core-metadata-platform`"""
supported_platforms: _Validator[Optional[List[str]]] = _Validator(added="1.1") supported_platforms: _Validator[list[str] | None] = _Validator(added="1.1")
""":external:ref:`core-metadata-supported-platform`""" """:external:ref:`core-metadata-supported-platform`"""
summary: _Validator[Optional[str]] = _Validator() summary: _Validator[str | None] = _Validator()
""":external:ref:`core-metadata-summary` (validated to contain no newlines)""" """:external:ref:`core-metadata-summary` (validated to contain no newlines)"""
description: _Validator[Optional[str]] = _Validator() # TODO 2.1: can be in body description: _Validator[str | None] = _Validator() # TODO 2.1: can be in body
""":external:ref:`core-metadata-description`""" """:external:ref:`core-metadata-description`"""
description_content_type: _Validator[Optional[str]] = _Validator(added="2.1") description_content_type: _Validator[str | None] = _Validator(added="2.1")
""":external:ref:`core-metadata-description-content-type` (validated)""" """:external:ref:`core-metadata-description-content-type` (validated)"""
keywords: _Validator[Optional[List[str]]] = _Validator() keywords: _Validator[list[str] | None] = _Validator()
""":external:ref:`core-metadata-keywords`""" """:external:ref:`core-metadata-keywords`"""
home_page: _Validator[Optional[str]] = _Validator() home_page: _Validator[str | None] = _Validator()
""":external:ref:`core-metadata-home-page`""" """:external:ref:`core-metadata-home-page`"""
download_url: _Validator[Optional[str]] = _Validator(added="1.1") download_url: _Validator[str | None] = _Validator(added="1.1")
""":external:ref:`core-metadata-download-url`""" """:external:ref:`core-metadata-download-url`"""
author: _Validator[Optional[str]] = _Validator() author: _Validator[str | None] = _Validator()
""":external:ref:`core-metadata-author`""" """:external:ref:`core-metadata-author`"""
author_email: _Validator[Optional[str]] = _Validator() author_email: _Validator[str | None] = _Validator()
""":external:ref:`core-metadata-author-email`""" """:external:ref:`core-metadata-author-email`"""
maintainer: _Validator[Optional[str]] = _Validator(added="1.2") maintainer: _Validator[str | None] = _Validator(added="1.2")
""":external:ref:`core-metadata-maintainer`""" """:external:ref:`core-metadata-maintainer`"""
maintainer_email: _Validator[Optional[str]] = _Validator(added="1.2") maintainer_email: _Validator[str | None] = _Validator(added="1.2")
""":external:ref:`core-metadata-maintainer-email`""" """:external:ref:`core-metadata-maintainer-email`"""
license: _Validator[Optional[str]] = _Validator() license: _Validator[str | None] = _Validator()
""":external:ref:`core-metadata-license`""" """:external:ref:`core-metadata-license`"""
classifiers: _Validator[Optional[List[str]]] = _Validator(added="1.1") classifiers: _Validator[list[str] | None] = _Validator(added="1.1")
""":external:ref:`core-metadata-classifier`""" """:external:ref:`core-metadata-classifier`"""
requires_dist: _Validator[Optional[List[requirements.Requirement]]] = _Validator( requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator(
added="1.2" added="1.2"
) )
""":external:ref:`core-metadata-requires-dist`""" """:external:ref:`core-metadata-requires-dist`"""
requires_python: _Validator[Optional[specifiers.SpecifierSet]] = _Validator( requires_python: _Validator[specifiers.SpecifierSet | None] = _Validator(
added="1.2" added="1.2"
) )
""":external:ref:`core-metadata-requires-python`""" """:external:ref:`core-metadata-requires-python`"""
# Because `Requires-External` allows for non-PEP 440 version specifiers, we # Because `Requires-External` allows for non-PEP 440 version specifiers, we
# don't do any processing on the values. # don't do any processing on the values.
requires_external: _Validator[Optional[List[str]]] = _Validator(added="1.2") requires_external: _Validator[list[str] | None] = _Validator(added="1.2")
""":external:ref:`core-metadata-requires-external`""" """:external:ref:`core-metadata-requires-external`"""
project_urls: _Validator[Optional[Dict[str, str]]] = _Validator(added="1.2") project_urls: _Validator[dict[str, str] | None] = _Validator(added="1.2")
""":external:ref:`core-metadata-project-url`""" """:external:ref:`core-metadata-project-url`"""
# PEP 685 lets us raise an error if an extra doesn't pass `Name` validation # PEP 685 lets us raise an error if an extra doesn't pass `Name` validation
# regardless of metadata version. # regardless of metadata version.
provides_extra: _Validator[Optional[List[utils.NormalizedName]]] = _Validator( provides_extra: _Validator[list[utils.NormalizedName] | None] = _Validator(
added="2.1", added="2.1",
) )
""":external:ref:`core-metadata-provides-extra`""" """:external:ref:`core-metadata-provides-extra`"""
provides_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2") provides_dist: _Validator[list[str] | None] = _Validator(added="1.2")
""":external:ref:`core-metadata-provides-dist`""" """:external:ref:`core-metadata-provides-dist`"""
obsoletes_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2") obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
""":external:ref:`core-metadata-obsoletes-dist`""" """:external:ref:`core-metadata-obsoletes-dist`"""
requires: _Validator[Optional[List[str]]] = _Validator(added="1.1") requires: _Validator[list[str] | None] = _Validator(added="1.1")
"""``Requires`` (deprecated)""" """``Requires`` (deprecated)"""
provides: _Validator[Optional[List[str]]] = _Validator(added="1.1") provides: _Validator[list[str] | None] = _Validator(added="1.1")
"""``Provides`` (deprecated)""" """``Provides`` (deprecated)"""
obsoletes: _Validator[Optional[List[str]]] = _Validator(added="1.1") obsoletes: _Validator[list[str] | None] = _Validator(added="1.1")
"""``Obsoletes`` (deprecated)""" """``Obsoletes`` (deprecated)"""

View file

@ -1,8 +1,9 @@
# This file is dual licensed under the terms of the Apache License, Version # 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 # 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details. # for complete details.
from __future__ import annotations
from typing import Any, Iterator, Optional, Set from typing import Any, Iterator
from ._parser import parse_requirement as _parse_requirement from ._parser import parse_requirement as _parse_requirement
from ._tokenizer import ParserSyntaxError from ._tokenizer import ParserSyntaxError
@ -37,10 +38,10 @@ class Requirement:
raise InvalidRequirement(str(e)) from e raise InvalidRequirement(str(e)) from e
self.name: str = parsed.name self.name: str = parsed.name
self.url: Optional[str] = parsed.url or None self.url: str | None = parsed.url or None
self.extras: Set[str] = set(parsed.extras or []) self.extras: set[str] = set(parsed.extras or [])
self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
self.marker: Optional[Marker] = None self.marker: Marker | None = None
if parsed.marker is not None: if parsed.marker is not None:
self.marker = Marker.__new__(Marker) self.marker = Marker.__new__(Marker)
self.marker._markers = _normalize_extra_values(parsed.marker) self.marker._markers = _normalize_extra_values(parsed.marker)

View file

@ -8,10 +8,12 @@
from packaging.version import Version from packaging.version import Version
""" """
from __future__ import annotations
import abc import abc
import itertools import itertools
import re import re
from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union from typing import Callable, Iterable, Iterator, TypeVar, Union
from .utils import canonicalize_version from .utils import canonicalize_version
from .version import Version from .version import Version
@ -64,7 +66,7 @@ class BaseSpecifier(metaclass=abc.ABCMeta):
@property @property
@abc.abstractmethod @abc.abstractmethod
def prereleases(self) -> Optional[bool]: def prereleases(self) -> bool | None:
"""Whether or not pre-releases as a whole are allowed. """Whether or not pre-releases as a whole are allowed.
This can be set to either ``True`` or ``False`` to explicitly enable or disable This can be set to either ``True`` or ``False`` to explicitly enable or disable
@ -79,14 +81,14 @@ class BaseSpecifier(metaclass=abc.ABCMeta):
""" """
@abc.abstractmethod @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. Determines if the given item is contained within this specifier.
""" """
@abc.abstractmethod @abc.abstractmethod
def filter( def filter(
self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
) -> Iterator[UnparsedVersionVar]: ) -> Iterator[UnparsedVersionVar]:
""" """
Takes an iterable of items and filters them so that only items which Takes an iterable of items and filters them so that only items which
@ -217,7 +219,7 @@ class Specifier(BaseSpecifier):
"===": "arbitrary", "===": "arbitrary",
} }
def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
"""Initialize a Specifier instance. """Initialize a Specifier instance.
:param spec: :param spec:
@ -234,7 +236,7 @@ class Specifier(BaseSpecifier):
if not match: if not match:
raise InvalidSpecifier(f"Invalid specifier: '{spec}'") raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
self._spec: Tuple[str, str] = ( self._spec: tuple[str, str] = (
match.group("operator").strip(), match.group("operator").strip(),
match.group("version").strip(), match.group("version").strip(),
) )
@ -318,7 +320,7 @@ class Specifier(BaseSpecifier):
return "{}{}".format(*self._spec) return "{}{}".format(*self._spec)
@property @property
def _canonical_spec(self) -> Tuple[str, str]: def _canonical_spec(self) -> tuple[str, str]:
canonical_version = canonicalize_version( canonical_version = canonicalize_version(
self._spec[1], self._spec[1],
strip_trailing_zero=(self._spec[0] != "~="), strip_trailing_zero=(self._spec[0] != "~="),
@ -364,7 +366,6 @@ class Specifier(BaseSpecifier):
return operator_callable return operator_callable
def _compare_compatible(self, prospective: Version, spec: str) -> bool: def _compare_compatible(self, prospective: Version, spec: str) -> bool:
# Compatible releases have an equivalent combination of >= and ==. That # Compatible releases have an equivalent combination of >= and ==. That
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
# implement this in terms of the other specifiers instead of # implement this in terms of the other specifiers instead of
@ -385,7 +386,6 @@ class Specifier(BaseSpecifier):
) )
def _compare_equal(self, prospective: Version, spec: str) -> bool: def _compare_equal(self, prospective: Version, spec: str) -> bool:
# We need special logic to handle prefix matching # 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. # In the case of prefix matching we want to ignore local segment.
@ -429,21 +429,18 @@ class Specifier(BaseSpecifier):
return not self._compare_equal(prospective, spec) return not self._compare_equal(prospective, spec)
def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
# NB: Local version identifiers are NOT permitted in the version # NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from # specifier, so local version labels can be universally removed from
# the prospective version. # the prospective version.
return Version(prospective.public) <= Version(spec) return Version(prospective.public) <= Version(spec)
def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
# NB: Local version identifiers are NOT permitted in the version # NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from # specifier, so local version labels can be universally removed from
# the prospective version. # the prospective version.
return Version(prospective.public) >= Version(spec) return Version(prospective.public) >= Version(spec)
def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
# Convert our spec to a Version instance, since we'll want to work with # Convert our spec to a Version instance, since we'll want to work with
# it as a version. # it as a version.
spec = Version(spec_str) spec = Version(spec_str)
@ -468,7 +465,6 @@ class Specifier(BaseSpecifier):
return True return True
def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
# Convert our spec to a Version instance, since we'll want to work with # Convert our spec to a Version instance, since we'll want to work with
# it as a version. # it as a version.
spec = Version(spec_str) spec = Version(spec_str)
@ -501,7 +497,7 @@ class Specifier(BaseSpecifier):
def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
return str(prospective).lower() == str(spec).lower() return str(prospective).lower() == str(spec).lower()
def __contains__(self, item: Union[str, Version]) -> bool: def __contains__(self, item: str | Version) -> bool:
"""Return whether or not the item is contained in this specifier. """Return whether or not the item is contained in this specifier.
:param item: The item to check for. :param item: The item to check for.
@ -522,9 +518,7 @@ class Specifier(BaseSpecifier):
""" """
return self.contains(item) return self.contains(item)
def contains( def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
self, item: UnparsedVersion, prereleases: Optional[bool] = None
) -> bool:
"""Return whether or not the item is contained in this specifier. """Return whether or not the item is contained in this specifier.
:param item: :param item:
@ -569,7 +563,7 @@ class Specifier(BaseSpecifier):
return operator_callable(normalized_item, self.version) return operator_callable(normalized_item, self.version)
def filter( def filter(
self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
) -> Iterator[UnparsedVersionVar]: ) -> Iterator[UnparsedVersionVar]:
"""Filter items in the given iterable, that match the specifier. """Filter items in the given iterable, that match the specifier.
@ -633,7 +627,7 @@ class Specifier(BaseSpecifier):
_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]: def _version_split(version: str) -> list[str]:
"""Split version into components. """Split version into components.
The split components are intended for version comparison. The logic does The split components are intended for version comparison. The logic does
@ -641,7 +635,7 @@ def _version_split(version: str) -> List[str]:
components back with :func:`_version_join` may not produce the original components back with :func:`_version_join` may not produce the original
version string. version string.
""" """
result: List[str] = [] result: list[str] = []
epoch, _, rest = version.rpartition("!") epoch, _, rest = version.rpartition("!")
result.append(epoch or "0") result.append(epoch or "0")
@ -655,7 +649,7 @@ def _version_split(version: str) -> List[str]:
return result return result
def _version_join(components: List[str]) -> str: def _version_join(components: list[str]) -> str:
"""Join split version components into a version string. """Join split version components into a version string.
This function assumes the input came from :func:`_version_split`, where the This function assumes the input came from :func:`_version_split`, where the
@ -672,7 +666,7 @@ def _is_not_suffix(segment: str) -> bool:
) )
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 = [], [] left_split, right_split = [], []
# Get the release segment of our versions # Get the release segment of our versions
@ -700,9 +694,7 @@ class SpecifierSet(BaseSpecifier):
specifiers (``>=3.0,!=3.1``), or no specifier at all. specifiers (``>=3.0,!=3.1``), or no specifier at all.
""" """
def __init__( def __init__(self, specifiers: str = "", prereleases: bool | None = None) -> None:
self, specifiers: str = "", prereleases: Optional[bool] = None
) -> None:
"""Initialize a SpecifierSet instance. """Initialize a SpecifierSet instance.
:param specifiers: :param specifiers:
@ -730,7 +722,7 @@ class SpecifierSet(BaseSpecifier):
self._prereleases = prereleases self._prereleases = prereleases
@property @property
def prereleases(self) -> Optional[bool]: def prereleases(self) -> bool | None:
# If we have been given an explicit prerelease modifier, then we'll # If we have been given an explicit prerelease modifier, then we'll
# pass that through here. # pass that through here.
if self._prereleases is not None: if self._prereleases is not None:
@ -787,7 +779,7 @@ class SpecifierSet(BaseSpecifier):
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(self._specs) return hash(self._specs)
def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
"""Return a SpecifierSet which is a combination of the two sets. """Return a SpecifierSet which is a combination of the two sets.
:param other: The other object to combine with. :param other: The other object to combine with.
@ -883,8 +875,8 @@ class SpecifierSet(BaseSpecifier):
def contains( def contains(
self, self,
item: UnparsedVersion, item: UnparsedVersion,
prereleases: Optional[bool] = None, prereleases: bool | None = None,
installed: Optional[bool] = None, installed: bool | None = None,
) -> bool: ) -> bool:
"""Return whether or not the item is contained in this SpecifierSet. """Return whether or not the item is contained in this SpecifierSet.
@ -938,7 +930,7 @@ class SpecifierSet(BaseSpecifier):
return all(s.contains(item, prereleases=prereleases) for s in self._specs) return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter( def filter(
self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
) -> Iterator[UnparsedVersionVar]: ) -> Iterator[UnparsedVersionVar]:
"""Filter items in the given iterable, that match the specifiers in this set. """Filter items in the given iterable, that match the specifiers in this set.
@ -995,8 +987,8 @@ class SpecifierSet(BaseSpecifier):
# which will filter out any pre-releases, unless there are no final # which will filter out any pre-releases, unless there are no final
# releases. # releases.
else: else:
filtered: List[UnparsedVersionVar] = [] filtered: list[UnparsedVersionVar] = []
found_prereleases: List[UnparsedVersionVar] = [] found_prereleases: list[UnparsedVersionVar] = []
for item in iterable: for item in iterable:
parsed_version = _coerce_version(item) parsed_version = _coerce_version(item)

View file

@ -2,6 +2,8 @@
# 2.0, and the BSD License. See the LICENSE file in the root of this repository # 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details. # for complete details.
from __future__ import annotations
import logging import logging
import platform import platform
import re import re
@ -11,15 +13,10 @@ import sys
import sysconfig import sysconfig
from importlib.machinery import EXTENSION_SUFFIXES from importlib.machinery import EXTENSION_SUFFIXES
from typing import ( from typing import (
Dict,
FrozenSet,
Iterable, Iterable,
Iterator, Iterator,
List,
Optional,
Sequence, Sequence,
Tuple, Tuple,
Union,
cast, cast,
) )
@ -30,7 +27,7 @@ logger = logging.getLogger(__name__)
PythonVersion = Sequence[int] PythonVersion = Sequence[int]
MacVersion = Tuple[int, int] MacVersion = Tuple[int, int]
INTERPRETER_SHORT_NAMES: Dict[str, str] = { INTERPRETER_SHORT_NAMES: dict[str, str] = {
"python": "py", # Generic. "python": "py", # Generic.
"cpython": "cp", "cpython": "cp",
"pypy": "pp", "pypy": "pp",
@ -96,7 +93,7 @@ class Tag:
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. Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
@ -112,8 +109,8 @@ def parse_tag(tag: str) -> FrozenSet[Tag]:
return frozenset(tags) 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: Union[int, str, None] = sysconfig.get_config_var(name) value: int | str | None = sysconfig.get_config_var(name)
if value is None and warn: if value is None and warn:
logger.debug( 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
@ -125,7 +122,7 @@ def _normalize_string(string: str) -> str:
return string.replace(".", "_").replace("-", "_").replace(" ", "_") return string.replace(".", "_").replace("-", "_").replace(" ", "_")
def _is_threaded_cpython(abis: List[str]) -> bool: def _is_threaded_cpython(abis: list[str]) -> bool:
""" """
Determine if the ABI corresponds to a threaded (`--disable-gil`) build. Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
@ -151,7 +148,7 @@ def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading 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]: def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
py_version = tuple(py_version) # To allow for version comparison. py_version = tuple(py_version) # To allow for version comparison.
abis = [] abis = []
version = _version_nodot(py_version[:2]) version = _version_nodot(py_version[:2])
@ -185,9 +182,9 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
def cpython_tags( def cpython_tags(
python_version: Optional[PythonVersion] = None, python_version: PythonVersion | None = None,
abis: Optional[Iterable[str]] = None, abis: Iterable[str] | None = None,
platforms: Optional[Iterable[str]] = None, platforms: Iterable[str] | None = None,
*, *,
warn: bool = False, warn: bool = False,
) -> Iterator[Tag]: ) -> Iterator[Tag]:
@ -244,7 +241,7 @@ def cpython_tags(
yield Tag(interpreter, "abi3", platform_) yield Tag(interpreter, "abi3", platform_)
def _generic_abi() -> List[str]: def _generic_abi() -> list[str]:
""" """
Return the ABI tag based on EXT_SUFFIX. Return the ABI tag based on EXT_SUFFIX.
""" """
@ -286,9 +283,9 @@ def _generic_abi() -> List[str]:
def generic_tags( def generic_tags(
interpreter: Optional[str] = None, interpreter: str | None = None,
abis: Optional[Iterable[str]] = None, abis: Iterable[str] | None = None,
platforms: Optional[Iterable[str]] = None, platforms: Iterable[str] | None = None,
*, *,
warn: bool = False, warn: bool = False,
) -> Iterator[Tag]: ) -> Iterator[Tag]:
@ -332,9 +329,9 @@ def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
def compatible_tags( def compatible_tags(
python_version: Optional[PythonVersion] = None, python_version: PythonVersion | None = None,
interpreter: Optional[str] = None, interpreter: str | None = None,
platforms: Optional[Iterable[str]] = None, platforms: Iterable[str] | None = None,
) -> Iterator[Tag]: ) -> Iterator[Tag]:
""" """
Yields the sequence of tags that are compatible with a specific version of Python. Yields the sequence of tags that are compatible with a specific version of Python.
@ -366,7 +363,7 @@ def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
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] formats = [cpu_arch]
if cpu_arch == "x86_64": if cpu_arch == "x86_64":
if version < (10, 4): if version < (10, 4):
@ -399,7 +396,7 @@ def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]:
def mac_platforms( def mac_platforms(
version: Optional[MacVersion] = None, arch: Optional[str] = None version: MacVersion | None = None, arch: str | None = None
) -> Iterator[str]: ) -> Iterator[str]:
""" """
Yields the platform tags for a macOS system. Yields the platform tags for a macOS system.

View file

@ -2,8 +2,10 @@
# 2.0, and the BSD License. See the LICENSE file in the root of this repository # 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details. # for complete details.
from __future__ import annotations
import re import re
from typing import FrozenSet, NewType, Tuple, Union, cast from typing import NewType, Tuple, Union, cast
from .tags import Tag, parse_tag from .tags import Tag, parse_tag
from .version import InvalidVersion, Version from .version import InvalidVersion, Version
@ -53,7 +55,7 @@ def is_normalized_name(name: str) -> bool:
def canonicalize_version( def canonicalize_version(
version: Union[Version, str], *, strip_trailing_zero: bool = True version: Version | str, *, strip_trailing_zero: bool = True
) -> str: ) -> str:
""" """
This is very similar to Version.__str__, but has one subtle difference This is very similar to Version.__str__, but has one subtle difference
@ -102,7 +104,7 @@ def canonicalize_version(
def parse_wheel_filename( def parse_wheel_filename(
filename: str, filename: str,
) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: ) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
if not filename.endswith(".whl"): if not filename.endswith(".whl"):
raise InvalidWheelFilename( raise InvalidWheelFilename(
f"Invalid wheel filename (extension must be '.whl'): {filename}" f"Invalid wheel filename (extension must be '.whl'): {filename}"
@ -143,7 +145,7 @@ def parse_wheel_filename(
return (name, version, build, tags) return (name, version, build, tags)
def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
if filename.endswith(".tar.gz"): if filename.endswith(".tar.gz"):
file_stem = filename[: -len(".tar.gz")] file_stem = filename[: -len(".tar.gz")]
elif filename.endswith(".zip"): elif filename.endswith(".zip"):

View file

@ -7,9 +7,11 @@
from packaging.version import parse, Version from packaging.version import parse, Version
""" """
from __future__ import annotations
import itertools import itertools
import re import re
from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, Union from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
@ -35,14 +37,14 @@ VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
class _Version(NamedTuple): class _Version(NamedTuple):
epoch: int epoch: int
release: Tuple[int, ...] release: tuple[int, ...]
dev: Optional[Tuple[str, int]] dev: tuple[str, int] | None
pre: Optional[Tuple[str, int]] pre: tuple[str, int] | None
post: Optional[Tuple[str, int]] post: tuple[str, int] | None
local: Optional[LocalType] local: LocalType | None
def parse(version: str) -> "Version": def parse(version: str) -> Version:
"""Parse the given version string. """Parse the given version string.
>>> parse('1.0.dev1') >>> parse('1.0.dev1')
@ -65,7 +67,7 @@ class InvalidVersion(ValueError):
class _BaseVersion: class _BaseVersion:
_key: Tuple[Any, ...] _key: tuple[Any, ...]
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(self._key) return hash(self._key)
@ -73,13 +75,13 @@ class _BaseVersion:
# Please keep the duplicated `isinstance` check # Please keep the duplicated `isinstance` check
# in the six comparisons hereunder # in the six comparisons hereunder
# unless you find a way to avoid adding overhead function calls. # 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): if not isinstance(other, _BaseVersion):
return NotImplemented return NotImplemented
return self._key < other._key return self._key < other._key
def __le__(self, other: "_BaseVersion") -> bool: def __le__(self, other: _BaseVersion) -> bool:
if not isinstance(other, _BaseVersion): if not isinstance(other, _BaseVersion):
return NotImplemented return NotImplemented
@ -91,13 +93,13 @@ class _BaseVersion:
return self._key == other._key return self._key == other._key
def __ge__(self, other: "_BaseVersion") -> bool: def __ge__(self, other: _BaseVersion) -> bool:
if not isinstance(other, _BaseVersion): if not isinstance(other, _BaseVersion):
return NotImplemented return NotImplemented
return self._key >= other._key return self._key >= other._key
def __gt__(self, other: "_BaseVersion") -> bool: def __gt__(self, other: _BaseVersion) -> bool:
if not isinstance(other, _BaseVersion): if not isinstance(other, _BaseVersion):
return NotImplemented return NotImplemented
@ -274,7 +276,7 @@ class Version(_BaseVersion):
return self._version.epoch return self._version.epoch
@property @property
def release(self) -> Tuple[int, ...]: def release(self) -> tuple[int, ...]:
"""The components of the "release" segment of the version. """The components of the "release" segment of the version.
>>> Version("1.2.3").release >>> Version("1.2.3").release
@ -290,7 +292,7 @@ class Version(_BaseVersion):
return self._version.release return self._version.release
@property @property
def pre(self) -> Optional[Tuple[str, int]]: def pre(self) -> tuple[str, int] | None:
"""The pre-release segment of the version. """The pre-release segment of the version.
>>> print(Version("1.2.3").pre) >>> print(Version("1.2.3").pre)
@ -305,7 +307,7 @@ class Version(_BaseVersion):
return self._version.pre return self._version.pre
@property @property
def post(self) -> Optional[int]: def post(self) -> int | None:
"""The post-release number of the version. """The post-release number of the version.
>>> print(Version("1.2.3").post) >>> print(Version("1.2.3").post)
@ -316,7 +318,7 @@ class Version(_BaseVersion):
return self._version.post[1] if self._version.post else None return self._version.post[1] if self._version.post else None
@property @property
def dev(self) -> Optional[int]: def dev(self) -> int | None:
"""The development number of the version. """The development number of the version.
>>> print(Version("1.2.3").dev) >>> print(Version("1.2.3").dev)
@ -327,7 +329,7 @@ class Version(_BaseVersion):
return self._version.dev[1] if self._version.dev else None return self._version.dev[1] if self._version.dev else None
@property @property
def local(self) -> Optional[str]: def local(self) -> str | None:
"""The local version segment of the version. """The local version segment of the version.
>>> print(Version("1.2.3").local) >>> print(Version("1.2.3").local)
@ -450,9 +452,8 @@ class Version(_BaseVersion):
def _parse_letter_version( def _parse_letter_version(
letter: Optional[str], number: Union[str, bytes, SupportsInt, None] letter: str | None, number: str | bytes | SupportsInt | None
) -> Optional[Tuple[str, int]]: ) -> tuple[str, int] | None:
if letter: if letter:
# We consider there to be an implicit 0 in a pre-release if there is # We consider there to be an implicit 0 in a pre-release if there is
# not a numeral associated with it. # not a numeral associated with it.
@ -488,7 +489,7 @@ def _parse_letter_version(
_local_version_separators = re.compile(r"[\._-]") _local_version_separators = re.compile(r"[\._-]")
def _parse_local_version(local: Optional[str]) -> Optional[LocalType]: def _parse_local_version(local: str | None) -> LocalType | None:
""" """
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
""" """
@ -502,13 +503,12 @@ def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:
def _cmpkey( def _cmpkey(
epoch: int, epoch: int,
release: Tuple[int, ...], release: tuple[int, ...],
pre: Optional[Tuple[str, int]], pre: tuple[str, int] | None,
post: Optional[Tuple[str, int]], post: tuple[str, int] | None,
dev: Optional[Tuple[str, int]], dev: tuple[str, int] | None,
local: Optional[LocalType], local: LocalType | None,
) -> CmpKey: ) -> CmpKey:
# When we compare a release version, we want to compare it with all of the # When we compare a release version, we want to compare it with all of the
# trailing zeros removed. So we'll use a reverse the list, drop all the now # trailing zeros removed. So we'll use a reverse the list, drop all the now
# leading zeros until we come to something non zero, then take the rest # leading zeros until we come to something non zero, then take the rest

View file

@ -23,7 +23,7 @@ IPy==1.01
Mako==1.3.5 Mako==1.3.5
MarkupSafe==2.1.5 MarkupSafe==2.1.5
musicbrainzngs==0.7.1 musicbrainzngs==0.7.1
packaging==24.0 packaging==24.1
paho-mqtt==2.1.0 paho-mqtt==2.1.0
platformdirs==4.2.2 platformdirs==4.2.2
plexapi==4.15.13 plexapi==4.15.13