diff options
| author | robot-piglet <[email protected]> | 2026-05-09 20:16:07 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2026-05-09 21:04:03 +0300 |
| commit | 6defa095368134f62323c48cabee1a95304cf742 (patch) | |
| tree | a508c87e5023c332e1a6dfdfa48204bf1752ff46 /contrib/python/packaging | |
| parent | e35e9fd54f83531a7344939b5313fcc40cfb89f0 (diff) | |
Intermediate changes
commit_hash:700c24e95953f7be1a6c425a35dbf7d41492469a
Diffstat (limited to 'contrib/python/packaging')
| -rw-r--r-- | contrib/python/packaging/py3/.dist-info/METADATA | 10 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/README.rst | 8 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/__init__.py | 2 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/_parser.py | 28 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/_structures.py | 33 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/markers.py | 39 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/metadata.py | 1 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/requirements.py | 34 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/specifiers.py | 156 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/tags.py | 56 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/packaging/version.py | 77 | ||||
| -rw-r--r-- | contrib/python/packaging/py3/ya.make | 3 |
12 files changed, 429 insertions, 18 deletions
diff --git a/contrib/python/packaging/py3/.dist-info/METADATA b/contrib/python/packaging/py3/.dist-info/METADATA index c1b3d36f8d2..d7ca4566284 100644 --- a/contrib/python/packaging/py3/.dist-info/METADATA +++ b/contrib/python/packaging/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: packaging -Version: 26.1 +Version: 26.2 Summary: Core utilities for Python packages Author-email: Donald Stufft <[email protected]> Requires-Python: >=3.8 @@ -53,10 +53,14 @@ The `documentation`_ provides information and the API for the following: - Version Handling - Specifiers - Markers +- Licenses - Requirements -- Tags - Metadata -- Lockfiles +- Tags +- Lockfiles (pylock) +- Direct URL helpers +- Dependency groups +- Errors - Utilities Installation diff --git a/contrib/python/packaging/py3/README.rst b/contrib/python/packaging/py3/README.rst index 4b47730e51a..c39830e124f 100644 --- a/contrib/python/packaging/py3/README.rst +++ b/contrib/python/packaging/py3/README.rst @@ -23,10 +23,14 @@ The `documentation`_ provides information and the API for the following: - Version Handling - Specifiers - Markers +- Licenses - Requirements -- Tags - Metadata -- Lockfiles +- Tags +- Lockfiles (pylock) +- Direct URL helpers +- Dependency groups +- Errors - Utilities Installation diff --git a/contrib/python/packaging/py3/packaging/__init__.py b/contrib/python/packaging/py3/packaging/__init__.py index 3c6400263b8..a6bdf59cd71 100644 --- a/contrib/python/packaging/py3/packaging/__init__.py +++ b/contrib/python/packaging/py3/packaging/__init__.py @@ -6,7 +6,7 @@ __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "26.1" +__version__ = "26.2" __author__ = "Donald Stufft and individual contributors" __email__ = "[email protected]" diff --git a/contrib/python/packaging/py3/packaging/_parser.py b/contrib/python/packaging/py3/packaging/_parser.py index f6c1f5cd226..d320269e153 100644 --- a/contrib/python/packaging/py3/packaging/_parser.py +++ b/contrib/python/packaging/py3/packaging/_parser.py @@ -27,6 +27,34 @@ class Node: def serialize(self) -> str: raise NotImplementedError + def __getstate__(self) -> str: + # Return just the value string for compactness and stability. + return self.value + + def _restore_value(self, value: object) -> None: + if not isinstance(value, str): + raise TypeError( + f"Cannot restore {self.__class__.__name__} value from {value!r}" + ) + self.value = value + + def __setstate__(self, state: object) -> None: + if isinstance(state, str): + # New format (26.2+): just the value string. + self._restore_value(state) + return + if isinstance(state, tuple) and len(state) == 2: + # Old format (packaging <= 26.0, __slots__): (None, {slot: value}). + _, slot_dict = state + if isinstance(slot_dict, dict) and "value" in slot_dict: + self._restore_value(slot_dict["value"]) + return + if isinstance(state, dict) and "value" in state: + # Old format (packaging <= 25.0, no __slots__): plain __dict__. + self._restore_value(state["value"]) + return + raise TypeError(f"Cannot restore {self.__class__.__name__} from {state!r}") + class Variable(Node): __slots__ = () diff --git a/contrib/python/packaging/py3/packaging/_structures.py b/contrib/python/packaging/py3/packaging/_structures.py new file mode 100644 index 00000000000..4306784d9a2 --- /dev/null +++ b/contrib/python/packaging/py3/packaging/_structures.py @@ -0,0 +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. + +"""Backward-compatibility shim for unpickling Version objects serialized before +packaging 26.1. + +Old pickles reference ``packaging._structures.InfinityType`` and +``packaging._structures.NegativeInfinityType``. This module provides minimal +stand-in classes so that ``pickle.loads()`` can resolve those references. +The deserialized objects are not used for comparisons — ``Version.__setstate__`` +discards the stale ``_key`` cache and recomputes it from the core version fields. +""" + +from __future__ import annotations + + +class InfinityType: + """Stand-in for the removed ``InfinityType`` used in old comparison keys.""" + + def __repr__(self) -> str: + return "Infinity" + + +class NegativeInfinityType: + """Stand-in for the removed ``NegativeInfinityType`` used in old comparison keys.""" + + def __repr__(self) -> str: + return "-Infinity" + + +Infinity = InfinityType() +NegativeInfinity = NegativeInfinityType() diff --git a/contrib/python/packaging/py3/packaging/markers.py b/contrib/python/packaging/py3/packaging/markers.py index 65d7f330b0c..564451f530b 100644 --- a/contrib/python/packaging/py3/packaging/markers.py +++ b/contrib/python/packaging/py3/packaging/markers.py @@ -322,6 +322,16 @@ class Marker: :param marker: The string representation of a marker expression. :raises InvalidMarker: If ``marker`` cannot be parsed. + + Instances are safe to serialize with :mod:`pickle`. They use a stable + format so the same pickle can be loaded in future packaging releases. + + .. versionchanged:: 26.2 + + Added a stable pickle format. Pickles created with packaging 26.2+ can + be unpickled with future releases. Backward compatibility with pickles + from packaging < 26.2 is supported but may be removed in a future + release. """ __slots__ = ("_markers",) @@ -381,6 +391,35 @@ class Marker: return str(self) == str(other) + def __getstate__(self) -> str: + # Return the marker expression string for compactness and stability. + # Internal Node objects are excluded; the string is re-parsed on load. + return str(self) + + def __setstate__(self, state: object) -> None: + if isinstance(state, str): + # New format (26.2+): just the marker expression string. + try: + self._markers = _normalize_extra_values(_parse_marker(state)) + except ParserSyntaxError as exc: + raise TypeError(f"Cannot restore Marker from {state!r}") from exc + return + if isinstance(state, dict) and "_markers" in state: + # Old format (packaging <= 26.1, no __slots__): plain __dict__. + markers = state["_markers"] + if isinstance(markers, list): + self._markers = markers + return + if isinstance(state, tuple) and len(state) == 2: + # Old format (packaging <= 26.1, __slots__): (None, {slot: value}). + _, slot_dict = state + if isinstance(slot_dict, dict) and "_markers" in slot_dict: + markers = slot_dict["_markers"] + if isinstance(markers, list): + self._markers = markers + return + raise TypeError(f"Cannot restore Marker from {state!r}") + def __and__(self, other: Marker) -> Marker: if not isinstance(other, Marker): return NotImplemented diff --git a/contrib/python/packaging/py3/packaging/metadata.py b/contrib/python/packaging/py3/packaging/metadata.py index b3269a45e9c..dccb627dea3 100644 --- a/contrib/python/packaging/py3/packaging/metadata.py +++ b/contrib/python/packaging/py3/packaging/metadata.py @@ -27,6 +27,7 @@ T = typing.TypeVar("T") __all__ = [ + "ExceptionGroup", # Keep this for a bit (makes mypy happy w/ 26.0 compat) "InvalidMetadata", "Metadata", "RFC822Message", diff --git a/contrib/python/packaging/py3/packaging/requirements.py b/contrib/python/packaging/py3/packaging/requirements.py index 18640d4386e..5892aee6ee2 100644 --- a/contrib/python/packaging/py3/packaging/requirements.py +++ b/contrib/python/packaging/py3/packaging/requirements.py @@ -33,6 +33,16 @@ class Requirement: Parse a given requirement string into its parts, such as name, specifier, URL, and extras. Raises InvalidRequirement on a badly-formed requirement string. + + Instances are safe to serialize with :mod:`pickle`. They use a stable + format so the same pickle can be loaded in future packaging releases. + + .. versionchanged:: 26.2 + + Added a stable pickle format. Pickles created with packaging 26.2+ can + be unpickled with future releases. Backward compatibility with pickles + from packaging < 26.2 is supported but may be removed in a future + release. """ # TODO: Can we test whether something is contained within a requirement? @@ -73,6 +83,30 @@ class Requirement: if self.marker: yield f"; {self.marker}" + def __getstate__(self) -> str: + # Return the requirement string for compactness and stability. + # Re-parsed on load to reconstruct all fields. + return str(self) + + def __setstate__(self, state: object) -> None: + if isinstance(state, str): + # New format (26.2+): just the requirement string. + try: + tmp = Requirement(state) + except InvalidRequirement as exc: + raise TypeError(f"Cannot restore Requirement from {state!r}") from exc + self.name = tmp.name + self.url = tmp.url + self.extras = tmp.extras + self.specifier = tmp.specifier + self.marker = tmp.marker + return + if isinstance(state, dict): + # Old format (packaging <= 26.1, no __slots__): plain __dict__. + self.__dict__.update(state) + return + raise TypeError(f"Cannot restore Requirement from {state!r}") + def __str__(self) -> str: return "".join(self._iter_parts(self.name)) diff --git a/contrib/python/packaging/py3/packaging/specifiers.py b/contrib/python/packaging/py3/packaging/specifiers.py index d0aa9a6cd33..b165dc0f7c8 100644 --- a/contrib/python/packaging/py3/packaging/specifiers.py +++ b/contrib/python/packaging/py3/packaging/specifiers.py @@ -15,12 +15,28 @@ import enum import functools import itertools import re +import sys import typing -from typing import Any, Callable, Final, Iterable, Iterator, Sequence, TypeVar, Union +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Final, + Iterable, + Iterator, + Sequence, + TypeVar, + Union, +) from .utils import canonicalize_version from .version import InvalidVersion, Version +if sys.version_info >= (3, 10): + from typing import TypeGuard # pragma: no cover +elif TYPE_CHECKING: + from typing_extensions import TypeGuard + __all__ = [ "BaseSpecifier", "InvalidSpecifier", @@ -33,6 +49,19 @@ def __dir__() -> list[str]: return __all__ +def _validate_spec(spec: object, /) -> TypeGuard[tuple[str, str]]: + return ( + isinstance(spec, tuple) + and len(spec) == 2 + and isinstance(spec[0], str) + and isinstance(spec[1], str) + ) + + +def _validate_pre(pre: object, /) -> TypeGuard[bool | None]: + return pre is None or isinstance(pre, bool) + + T = TypeVar("T") UnparsedVersion = Union[Version, str] UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) @@ -414,6 +443,16 @@ class Specifier(BaseSpecifier): It is generally not required to instantiate this manually. You should instead prefer to work with :class:`SpecifierSet` instead, which can parse comma-separated version specifiers (which is what package metadata contains). + + Instances are safe to serialize with :mod:`pickle`. They use a stable + format so the same pickle can be loaded in future packaging releases. + + .. versionchanged:: 26.2 + + Added a stable pickle format. Pickles created with packaging 26.2+ can + be unpickled with future releases. Backward compatibility with pickles + from packaging < 26.2 is supported but may be removed in a future + release. """ __slots__ = ( @@ -722,6 +761,46 @@ class Specifier(BaseSpecifier): def prereleases(self, value: bool | None) -> None: self._prereleases = value + def __getstate__(self) -> tuple[tuple[str, str], bool | None]: + # Return state as a 2-item tuple for compactness: + # ((operator, version), prereleases) + # Cache members are excluded and will be recomputed on demand. + return (self._spec, self._prereleases) + + def __setstate__(self, state: object) -> None: + # Always discard cached values - they will be recomputed on demand. + self._spec_version = None + self._wildcard_split = None + self._ranges = None + + if isinstance(state, tuple): + if len(state) == 2: + # New format (26.2+): ((operator, version), prereleases) + spec, prereleases = state + if _validate_spec(spec) and _validate_pre(prereleases): + self._spec = spec + self._prereleases = prereleases + return + if len(state) == 2 and isinstance(state[1], dict): + # Format (packaging 26.0-26.1): (None, {slot: value}). + _, slot_dict = state + spec = slot_dict.get("_spec") + prereleases = slot_dict.get("_prereleases", "invalid") + if _validate_spec(spec) and _validate_pre(prereleases): + self._spec = spec + self._prereleases = prereleases + return + if isinstance(state, dict): + # Old format (packaging <= 25.x, no __slots__): state is a plain dict. + spec = state.get("_spec") + prereleases = state.get("_prereleases", "invalid") + if _validate_spec(spec) and _validate_pre(prereleases): + self._spec = spec + self._prereleases = prereleases + return + + raise TypeError(f"Cannot restore Specifier from {state!r}") + @property def operator(self) -> str: """The operator of this specifier. @@ -1257,6 +1336,18 @@ class SpecifierSet(BaseSpecifier): It can be passed a single specifier (``>=3.0``), a comma-separated list of specifiers (``>=3.0,!=3.1``), or no specifier at all. + + Instances are safe to serialize with :mod:`pickle`. They use a stable + format so the same pickle can be loaded in future packaging + releases. + + .. versionchanged:: 26.2 + + Added a stable pickle format. Pickles created with + packaging 26.2+ can be unpickled with future releases. + Backward compatibility with pickles from + packaging < 26.2 is supported but may be removed in a future + release. """ __slots__ = ( @@ -1347,6 +1438,69 @@ class SpecifierSet(BaseSpecifier): self._prereleases = value self._is_unsatisfiable = None + def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]: + # Return state as a 2-item tuple for compactness: + # (specs, prereleases) + # Cache members are excluded and will be recomputed on demand. + return (self._specs, self._prereleases) + + def __setstate__(self, state: object) -> None: + # Always discard cached values - they will be recomputed on demand. + self._resolved_ops = None + self._is_unsatisfiable = None + + if isinstance(state, tuple): + if len(state) == 2: + # New format (26.2+): (specs, prereleases) + specs, prereleases = state + if ( + isinstance(specs, tuple) + and all(isinstance(s, Specifier) for s in specs) + and _validate_pre(prereleases) + ): + self._specs = specs + self._prereleases = prereleases + self._canonicalized = len(specs) <= 1 + self._has_arbitrary = any("===" in str(s) for s in specs) + return + if len(state) == 2 and isinstance(state[1], dict): + # Format (packaging 26.0-26.1): (None, {slot: value}). + _, slot_dict = state + specs = slot_dict.get("_specs", ()) + prereleases = slot_dict.get("_prereleases") + # Convert frozenset to tuple (26.0 stored as frozenset) + if isinstance(specs, frozenset): + specs = tuple(sorted(specs, key=str)) + if ( + isinstance(specs, tuple) + and all(isinstance(s, Specifier) for s in specs) + and _validate_pre(prereleases) + ): + self._specs = specs + self._prereleases = prereleases + self._canonicalized = len(self._specs) <= 1 + self._has_arbitrary = any("===" in str(s) for s in self._specs) + return + if isinstance(state, dict): + # Old format (packaging <= 25.x, no __slots__): state is a plain dict. + specs = state.get("_specs", ()) + prereleases = state.get("_prereleases") + # Convert frozenset to tuple (26.0 stored as frozenset) + if isinstance(specs, frozenset): + specs = tuple(sorted(specs, key=str)) + if ( + isinstance(specs, tuple) + and all(isinstance(s, Specifier) for s in specs) + and _validate_pre(prereleases) + ): + self._specs = specs + self._prereleases = prereleases + self._canonicalized = len(self._specs) <= 1 + self._has_arbitrary = any("===" in str(s) for s in self._specs) + return + + raise TypeError(f"Cannot restore SpecifierSet from {state!r}") + def __repr__(self) -> str: """A representation of the specifier set that shows all internal state. diff --git a/contrib/python/packaging/py3/packaging/tags.py b/contrib/python/packaging/py3/packaging/tags.py index 7f71e3910e5..9980ab3c63c 100644 --- a/contrib/python/packaging/py3/packaging/tags.py +++ b/contrib/python/packaging/py3/packaging/tags.py @@ -15,7 +15,6 @@ import sysconfig from importlib.machinery import EXTENSION_SUFFIXES from typing import ( TYPE_CHECKING, - Any, Iterable, Iterator, Sequence, @@ -92,6 +91,16 @@ class Tag: Instances are considered immutable and thus are hashable. Equality checking is also supported. + + Instances are safe to serialize with :mod:`pickle`. They use a stable + format so the same pickle can be loaded in future packaging releases. + + .. versionchanged:: 26.2 + + Added a stable pickle format. Pickles created with packaging 26.2+ can + be unpickled with future releases. Backward compatibility with pickles + from packaging < 26.2 is supported but may be removed in a future + release. """ __slots__ = ["_abi", "_hash", "_interpreter", "_platform"] @@ -158,12 +167,37 @@ class Tag: def __repr__(self) -> str: return f"<{self} @ {id(self)}>" - def __setstate__(self, state: tuple[None, dict[str, Any]]) -> None: - # The cached _hash is wrong when unpickling. - _, slots = state - for k, v in slots.items(): - setattr(self, k, v) - self._hash = hash((self._interpreter, self._abi, self._platform)) + def __getstate__(self) -> tuple[str, str, str]: + # Return state as a 3-item tuple: (interpreter, abi, platform). + # Cache member _hash is excluded and will be recomputed. + return (self._interpreter, self._abi, self._platform) + + def __setstate__(self, state: object) -> None: + if isinstance(state, tuple): + if len(state) == 3 and all(isinstance(s, str) for s in state): + # New format (26.2+): (interpreter, abi, platform) + self._interpreter, self._abi, self._platform = state + self._hash = hash((self._interpreter, self._abi, self._platform)) + return + if len(state) == 2 and isinstance(state[1], dict): + # Old format (packaging <= 26.1, __slots__): (None, {slot: value}). + _, slots = state + try: + interpreter = slots["_interpreter"] + abi = slots["_abi"] + platform = slots["_platform"] + except KeyError: + raise TypeError(f"Cannot restore Tag from {state!r}") from None + if not all( + isinstance(value, str) for value in (interpreter, abi, platform) + ): + raise TypeError(f"Cannot restore Tag from {state!r}") + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + self._hash = hash((self._interpreter, self._abi, self._platform)) + return + raise TypeError(f"Cannot restore Tag from {state!r}") def parse_tag(tag: str, *, validate_order: bool = False) -> frozenset[Tag]: @@ -749,9 +783,11 @@ def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: def _emscripten_platforms() -> Iterator[str]: - pyemscripten_abi_version = sysconfig.get_config_var("PYEMSCRIPTEN_ABI_VERSION") - if pyemscripten_abi_version: - yield f"pyemscripten_{pyemscripten_abi_version}_wasm32" + pyemscripten_platform_version = sysconfig.get_config_var( + "PYEMSCRIPTEN_PLATFORM_VERSION" + ) + if pyemscripten_platform_version: + yield f"pyemscripten_{pyemscripten_platform_version}_wasm32" yield from _generic_platforms() diff --git a/contrib/python/packaging/py3/packaging/version.py b/contrib/python/packaging/py3/packaging/version.py index f872b85df90..97f23fd9ad6 100644 --- a/contrib/python/packaging/py3/packaging/version.py +++ b/contrib/python/packaging/py3/packaging/version.py @@ -358,6 +358,16 @@ class Version(_BaseVersion): :class:`Version` is immutable; use :meth:`__replace__` to change part of a version. + + Instances are safe to serialize with :mod:`pickle`. They use a stable + format so the same pickle can be loaded in future packaging releases. + + .. versionchanged:: 26.2 + + Added a stable pickle format. Pickles created with packaging 26.2+ can + be unpickled with future releases. Backward compatibility with pickles + from packaging < 26.2 is supported but may be removed in a future + release. """ __slots__ = ( @@ -741,6 +751,73 @@ class Version(_BaseVersion): return super().__ne__(other) + def __getstate__( + self, + ) -> tuple[ + int, + tuple[int, ...], + tuple[str, int] | None, + tuple[str, int] | None, + tuple[str, int] | None, + LocalType | None, + ]: + # Return state as a 6-item tuple for compactness: + # (epoch, release, pre, post, dev, local) + # Cache members are excluded and will be recomputed on demand + return ( + self._epoch, + self._release, + self._pre, + self._post, + self._dev, + self._local, + ) + + def __setstate__(self, state: object) -> None: + # Always discard cached values — they may contain stale references + # (e.g. packaging._structures.InfinityType from pre-26.1 pickles) + # and will be recomputed on demand from the core fields above. + self._key_cache = None + self._hash_cache = None + + if isinstance(state, tuple): + if len(state) == 6: + # New format (26.2+): (epoch, release, pre, post, dev, local) + ( + self._epoch, + self._release, + self._pre, + self._post, + self._dev, + self._local, + ) = state + return + if len(state) == 2: + # Format (packaging 26.0-26.1): (None, {slot: value}). + _, slot_dict = state + if isinstance(slot_dict, dict): + self._epoch = slot_dict["_epoch"] + self._release = slot_dict["_release"] + self._pre = slot_dict.get("_pre") + self._post = slot_dict.get("_post") + self._dev = slot_dict.get("_dev") + self._local = slot_dict.get("_local") + return + if isinstance(state, dict): + # Old format (packaging <= 25.x, no __slots__): state is a plain + # dict with "_version" (_Version NamedTuple) and "_key" entries. + version_nt = state.get("_version") + if version_nt is not None: + self._epoch = version_nt.epoch + self._release = version_nt.release + self._pre = version_nt.pre + self._post = version_nt.post + self._dev = version_nt.dev + self._local = version_nt.local + return + + raise TypeError(f"Cannot restore Version from {state!r}") + @property @_deprecated("Version._version is private and will be removed soon") def _version(self) -> _Version: diff --git a/contrib/python/packaging/py3/ya.make b/contrib/python/packaging/py3/ya.make index d1ccae20d2c..c8b64dc7944 100644 --- a/contrib/python/packaging/py3/ya.make +++ b/contrib/python/packaging/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(26.1) +VERSION(26.2) LICENSE(BSD-2-Clause AND Apache-2.0) @@ -15,6 +15,7 @@ PY_SRCS( packaging/_manylinux.py packaging/_musllinux.py packaging/_parser.py + packaging/_structures.py packaging/_tokenizer.py packaging/dependency_groups.py packaging/direct_url.py |
