From 9b38c6698a3d6e7b37279dd9ae0d4d6430f9c612 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 1 Sep 2025 11:27:01 -0400 Subject: [PATCH 1/6] Reapply "gh-132947: Apply changes from importlib_metadata 8.7 (#137885)" (#137924) (#137930) * Reapply "gh-132947: Apply changes from importlib_metadata 8.7 (#137885)" (#137924) This reverts commit 3706ef66eff2b9e24091dbe050f5dbc60785b481. * Skip the triggering test on buildbots only. --- Lib/importlib/metadata/__init__.py | 205 ++++++++++++------ Lib/importlib/metadata/_adapters.py | 58 ++++- Lib/importlib/metadata/_collections.py | 6 +- Lib/importlib/metadata/_functools.py | 2 +- Lib/importlib/metadata/_meta.py | 24 +- Lib/importlib/metadata/_typing.py | 15 ++ .../test_importlib/metadata/_issue138313.py | 15 ++ Lib/test/test_importlib/metadata/_path.py | 44 ++-- Lib/test/test_importlib/metadata/fixtures.py | 20 +- Lib/test/test_importlib/metadata/test_api.py | 5 +- Lib/test/test_importlib/metadata/test_main.py | 22 +- Lib/test/test_importlib/metadata/test_zip.py | 3 +- ...-08-17-10-22-31.gh-issue-132947.XR4MJ8.rst | 6 + 13 files changed, 307 insertions(+), 118 deletions(-) create mode 100644 Lib/importlib/metadata/_typing.py create mode 100644 Lib/test/test_importlib/metadata/_issue138313.py create mode 100644 Misc/NEWS.d/next/Library/2025-08-17-10-22-31.gh-issue-132947.XR4MJ8.rst diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index b59587e80165e5..1e2cea4009482a 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -1,33 +1,40 @@ +""" +APIs exposing metadata from third-party Python packages. + +This codebase is shared between importlib.metadata in the stdlib +and importlib_metadata in PyPI. See +https://github.com/python/importlib_metadata/wiki/Development-Methodology +for more detail. +""" + from __future__ import annotations -import os -import re import abc -import sys -import json +import collections import email -import types -import inspect -import pathlib -import zipfile -import operator -import textwrap import functools import itertools +import operator +import os +import pathlib import posixpath -import collections +import re +import sys +import textwrap +import types +from collections.abc import Iterable, Mapping +from contextlib import suppress +from importlib import import_module +from importlib.abc import MetaPathFinder +from itertools import starmap +from typing import Any from . import _meta from ._collections import FreezableDefaultDict, Pair from ._functools import method_cache, pass_none from ._itertools import always_iterable, bucket, unique_everseen from ._meta import PackageMetadata, SimplePath - -from contextlib import suppress -from importlib import import_module -from importlib.abc import MetaPathFinder -from itertools import starmap -from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast +from ._typing import md_none __all__ = [ 'Distribution', @@ -53,7 +60,7 @@ def __str__(self) -> str: return f"No package metadata was found for {self.name}" @property - def name(self) -> str: # type: ignore[override] + def name(self) -> str: # type: ignore[override] # make readonly (name,) = self.args return name @@ -123,6 +130,12 @@ def valid(line: str): return line and not line.startswith('#') +class _EntryPointMatch(types.SimpleNamespace): + module: str + attr: str + extras: str + + class EntryPoint: """An entry point as defined by Python packaging conventions. @@ -138,6 +151,30 @@ class EntryPoint: 'attr' >>> ep.extras ['extra1', 'extra2'] + + If the value package or module are not valid identifiers, a + ValueError is raised on access. + + >>> EntryPoint(name=None, group=None, value='invalid-name').module + Traceback (most recent call last): + ... + ValueError: ('Invalid object reference...invalid-name... + >>> EntryPoint(name=None, group=None, value='invalid-name').attr + Traceback (most recent call last): + ... + ValueError: ('Invalid object reference...invalid-name... + >>> EntryPoint(name=None, group=None, value='invalid-name').extras + Traceback (most recent call last): + ... + ValueError: ('Invalid object reference...invalid-name... + + The same thing happens on construction. + + >>> EntryPoint(name=None, group=None, value='invalid-name') + Traceback (most recent call last): + ... + ValueError: ('Invalid object reference...invalid-name... + """ pattern = re.compile( @@ -165,38 +202,44 @@ class EntryPoint: value: str group: str - dist: Optional[Distribution] = None + dist: Distribution | None = None def __init__(self, name: str, value: str, group: str) -> None: vars(self).update(name=name, value=value, group=group) + self.module def load(self) -> Any: """Load the entry point from its definition. If only a module is indicated by the value, return that module. Otherwise, return the named object. """ - match = cast(Match, self.pattern.match(self.value)) - module = import_module(match.group('module')) - attrs = filter(None, (match.group('attr') or '').split('.')) + module = import_module(self.module) + attrs = filter(None, (self.attr or '').split('.')) return functools.reduce(getattr, attrs, module) @property def module(self) -> str: - match = self.pattern.match(self.value) - assert match is not None - return match.group('module') + return self._match.module @property def attr(self) -> str: - match = self.pattern.match(self.value) - assert match is not None - return match.group('attr') + return self._match.attr @property - def extras(self) -> List[str]: + def extras(self) -> list[str]: + return re.findall(r'\w+', self._match.extras or '') + + @functools.cached_property + def _match(self) -> _EntryPointMatch: match = self.pattern.match(self.value) - assert match is not None - return re.findall(r'\w+', match.group('extras') or '') + if not match: + raise ValueError( + 'Invalid object reference. ' + 'See https://packaging.python.org' + '/en/latest/specifications/entry-points/#data-model', + self.value, + ) + return _EntryPointMatch(**match.groupdict()) def _for(self, dist): vars(self).update(dist=dist) @@ -222,9 +265,26 @@ def matches(self, **params): >>> ep.matches(attr='bong') True """ + self._disallow_dist(params) attrs = (getattr(self, param) for param in params) return all(map(operator.eq, params.values(), attrs)) + @staticmethod + def _disallow_dist(params): + """ + Querying by dist is not allowed (dist objects are not comparable). + >>> EntryPoint(name='fan', value='fav', group='fag').matches(dist='foo') + Traceback (most recent call last): + ... + ValueError: "dist" is not suitable for matching... + """ + if "dist" in params: + raise ValueError( + '"dist" is not suitable for matching. ' + "Instead, use Distribution.entry_points.select() on a " + "located distribution." + ) + def _key(self): return self.name, self.value, self.group @@ -254,7 +314,7 @@ class EntryPoints(tuple): __slots__ = () - def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] + def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] # Work with str instead of int """ Get the EntryPoint in self matching name. """ @@ -278,14 +338,14 @@ def select(self, **params) -> EntryPoints: return EntryPoints(ep for ep in self if ep.matches(**params)) @property - def names(self) -> Set[str]: + def names(self) -> set[str]: """ Return the set of all names of all entry points. """ return {ep.name for ep in self} @property - def groups(self) -> Set[str]: + def groups(self) -> set[str]: """ Return the set of all groups of all entry points. """ @@ -306,11 +366,11 @@ def _from_text(text): class PackagePath(pathlib.PurePosixPath): """A reference to a path in a package""" - hash: Optional[FileHash] + hash: FileHash | None size: int dist: Distribution - def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override] + def read_text(self, encoding: str = 'utf-8') -> str: return self.locate().read_text(encoding=encoding) def read_binary(self) -> bytes: @@ -341,7 +401,7 @@ class Distribution(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def read_text(self, filename) -> Optional[str]: + def read_text(self, filename) -> str | None: """Attempt to load metadata file given by the name. Python distribution metadata is organized by blobs of text @@ -368,6 +428,17 @@ def locate_file(self, path: str | os.PathLike[str]) -> SimplePath: """ Given a path to a file in this distribution, return a SimplePath to it. + + This method is used by callers of ``Distribution.files()`` to + locate files within the distribution. If it's possible for a + Distribution to represent files in the distribution as + ``SimplePath`` objects, it should implement this method + to resolve such objects. + + Some Distribution providers may elect not to resolve SimplePath + objects within the distribution by raising a + NotImplementedError, but consumers of such a Distribution would + be unable to invoke ``Distribution.files()``. """ @classmethod @@ -390,7 +461,7 @@ def from_name(cls, name: str) -> Distribution: @classmethod def discover( - cls, *, context: Optional[DistributionFinder.Context] = None, **kwargs + cls, *, context: DistributionFinder.Context | None = None, **kwargs ) -> Iterable[Distribution]: """Return an iterable of Distribution objects for all packages. @@ -436,7 +507,7 @@ def _discover_resolvers(): return filter(None, declared) @property - def metadata(self) -> _meta.PackageMetadata: + def metadata(self) -> _meta.PackageMetadata | None: """Return the parsed metadata for this Distribution. The returned object will have keys that name the various bits of @@ -446,10 +517,8 @@ def metadata(self) -> _meta.PackageMetadata: Custom providers may provide the METADATA file or override this property. """ - # deferred for performance (python/cpython#109829) - from . import _adapters - opt_text = ( + text = ( self.read_text('METADATA') or self.read_text('PKG-INFO') # This last clause is here to support old egg-info files. Its @@ -457,13 +526,20 @@ def metadata(self) -> _meta.PackageMetadata: # (which points to the egg-info file) attribute unchanged. or self.read_text('') ) - text = cast(str, opt_text) + return self._assemble_message(text) + + @staticmethod + @pass_none + def _assemble_message(text: str) -> _meta.PackageMetadata: + # deferred for performance (python/cpython#109829) + from . import _adapters + return _adapters.Message(email.message_from_string(text)) @property def name(self) -> str: """Return the 'Name' metadata for the distribution package.""" - return self.metadata['Name'] + return md_none(self.metadata)['Name'] @property def _normalized_name(self): @@ -473,7 +549,7 @@ def _normalized_name(self): @property def version(self) -> str: """Return the 'Version' metadata for the distribution package.""" - return self.metadata['Version'] + return md_none(self.metadata)['Version'] @property def entry_points(self) -> EntryPoints: @@ -486,7 +562,7 @@ def entry_points(self) -> EntryPoints: return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self) @property - def files(self) -> Optional[List[PackagePath]]: + def files(self) -> list[PackagePath] | None: """Files in this distribution. :return: List of PackagePath for this distribution or None @@ -579,7 +655,7 @@ def _read_files_egginfo_sources(self): return text and map('"{}"'.format, text.splitlines()) @property - def requires(self) -> Optional[List[str]]: + def requires(self) -> list[str] | None: """Generated requirements specified for this Distribution""" reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() return reqs and list(reqs) @@ -635,6 +711,9 @@ def origin(self): return self._load_json('direct_url.json') def _load_json(self, filename): + # Deferred for performance (python/importlib_metadata#503) + import json + return pass_none(json.loads)( self.read_text(filename), object_hook=lambda data: types.SimpleNamespace(**data), @@ -682,7 +761,7 @@ def __init__(self, **kwargs): vars(self).update(kwargs) @property - def path(self) -> List[str]: + def path(self) -> list[str]: """ The sequence of directory path that a distribution finder should search. @@ -719,7 +798,7 @@ class FastPath: True """ - @functools.lru_cache() # type: ignore + @functools.lru_cache() # type: ignore[misc] def __new__(cls, root): return super().__new__(cls) @@ -737,6 +816,9 @@ def children(self): return [] def zip_children(self): + # deferred for performance (python/importlib_metadata#502) + import zipfile + zip_path = zipfile.Path(self.root) names = zip_path.root.namelist() self.joinpath = zip_path.joinpath @@ -831,7 +913,7 @@ class Prepared: normalized = None legacy_normalized = None - def __init__(self, name: Optional[str]): + def __init__(self, name: str | None): self.name = name if name is None: return @@ -894,7 +976,7 @@ def __init__(self, path: SimplePath) -> None: """ self._path = path - def read_text(self, filename: str | os.PathLike[str]) -> Optional[str]: + def read_text(self, filename: str | os.PathLike[str]) -> str | None: with suppress( FileNotFoundError, IsADirectoryError, @@ -958,7 +1040,7 @@ def distributions(**kwargs) -> Iterable[Distribution]: return Distribution.discover(**kwargs) -def metadata(distribution_name: str) -> _meta.PackageMetadata: +def metadata(distribution_name: str) -> _meta.PackageMetadata | None: """Get the metadata for the named package. :param distribution_name: The name of the distribution package to query. @@ -1001,7 +1083,7 @@ def entry_points(**params) -> EntryPoints: return EntryPoints(eps).select(**params) -def files(distribution_name: str) -> Optional[List[PackagePath]]: +def files(distribution_name: str) -> list[PackagePath] | None: """Return a list of files for the named package. :param distribution_name: The name of the distribution package to query. @@ -1010,7 +1092,7 @@ def files(distribution_name: str) -> Optional[List[PackagePath]]: return distribution(distribution_name).files -def requires(distribution_name: str) -> Optional[List[str]]: +def requires(distribution_name: str) -> list[str] | None: """ Return a list of requirements for the named package. @@ -1020,7 +1102,7 @@ def requires(distribution_name: str) -> Optional[List[str]]: return distribution(distribution_name).requires -def packages_distributions() -> Mapping[str, List[str]]: +def packages_distributions() -> Mapping[str, list[str]]: """ Return a mapping of top-level packages to their distributions. @@ -1033,7 +1115,7 @@ def packages_distributions() -> Mapping[str, List[str]]: pkg_to_dist = collections.defaultdict(list) for dist in distributions(): for pkg in _top_level_declared(dist) or _top_level_inferred(dist): - pkg_to_dist[pkg].append(dist.metadata['Name']) + pkg_to_dist[pkg].append(md_none(dist.metadata)['Name']) return dict(pkg_to_dist) @@ -1041,7 +1123,7 @@ def _top_level_declared(dist): return (dist.read_text('top_level.txt') or '').split() -def _topmost(name: PackagePath) -> Optional[str]: +def _topmost(name: PackagePath) -> str | None: """ Return the top-most parent as long as there is a parent. """ @@ -1067,11 +1149,10 @@ def _get_toplevel_name(name: PackagePath) -> str: >>> _get_toplevel_name(PackagePath('foo.dist-info')) 'foo.dist-info' """ - return _topmost(name) or ( - # python/typeshed#10328 - inspect.getmodulename(name) # type: ignore - or str(name) - ) + # Defer import of inspect for performance (python/cpython#118761) + import inspect + + return _topmost(name) or inspect.getmodulename(name) or str(name) def _top_level_inferred(dist): diff --git a/Lib/importlib/metadata/_adapters.py b/Lib/importlib/metadata/_adapters.py index 6223263ed53f22..f5b30dd92cde69 100644 --- a/Lib/importlib/metadata/_adapters.py +++ b/Lib/importlib/metadata/_adapters.py @@ -1,11 +1,58 @@ +import email.message +import email.policy import re import textwrap -import email.message from ._text import FoldedCase +class RawPolicy(email.policy.EmailPolicy): + def fold(self, name, value): + folded = self.linesep.join( + textwrap.indent(value, prefix=' ' * 8, predicate=lambda line: True) + .lstrip() + .splitlines() + ) + return f'{name}: {folded}{self.linesep}' + + class Message(email.message.Message): + r""" + Specialized Message subclass to handle metadata naturally. + + Reads values that may have newlines in them and converts the + payload to the Description. + + >>> msg_text = textwrap.dedent(''' + ... Name: Foo + ... Version: 3.0 + ... License: blah + ... de-blah + ... + ... First line of description. + ... Second line of description. + ... + ... Fourth line! + ... ''').lstrip().replace('', '') + >>> msg = Message(email.message_from_string(msg_text)) + >>> msg['Description'] + 'First line of description.\nSecond line of description.\n\nFourth line!\n' + + Message should render even if values contain newlines. + + >>> print(msg) + Name: Foo + Version: 3.0 + License: blah + de-blah + Description: First line of description. + Second line of description. + + Fourth line! + + + """ + multiple_use_keys = set( map( FoldedCase, @@ -57,15 +104,20 @@ def __getitem__(self, item): def _repair_headers(self): def redent(value): "Correct for RFC822 indentation" - if not value or '\n' not in value: + indent = ' ' * 8 + if not value or '\n' + indent not in value: return value - return textwrap.dedent(' ' * 8 + value) + return textwrap.dedent(indent + value) headers = [(key, redent(value)) for key, value in vars(self)['_headers']] if self._payload: headers.append(('Description', self.get_payload())) + self.set_payload('') return headers + def as_string(self): + return super().as_string(policy=RawPolicy()) + @property def json(self): """ diff --git a/Lib/importlib/metadata/_collections.py b/Lib/importlib/metadata/_collections.py index cf0954e1a30546..fc5045d36be572 100644 --- a/Lib/importlib/metadata/_collections.py +++ b/Lib/importlib/metadata/_collections.py @@ -1,4 +1,5 @@ import collections +import typing # from jaraco.collections 3.3 @@ -24,7 +25,10 @@ def freeze(self): self._frozen = lambda key: self.default_factory() -class Pair(collections.namedtuple('Pair', 'name value')): +class Pair(typing.NamedTuple): + name: str + value: str + @classmethod def parse(cls, text): return cls(*map(str.strip, text.split("=", 1))) diff --git a/Lib/importlib/metadata/_functools.py b/Lib/importlib/metadata/_functools.py index 71f66bd03cb713..5dda6a2199ad0b 100644 --- a/Lib/importlib/metadata/_functools.py +++ b/Lib/importlib/metadata/_functools.py @@ -1,5 +1,5 @@ -import types import functools +import types # from jaraco.functools 3.3 diff --git a/Lib/importlib/metadata/_meta.py b/Lib/importlib/metadata/_meta.py index 1927d0f624d82f..0c20eff3da7522 100644 --- a/Lib/importlib/metadata/_meta.py +++ b/Lib/importlib/metadata/_meta.py @@ -1,9 +1,13 @@ from __future__ import annotations import os -from typing import Protocol -from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload - +from collections.abc import Iterator +from typing import ( + Any, + Protocol, + TypeVar, + overload, +) _T = TypeVar("_T") @@ -20,25 +24,25 @@ def __iter__(self) -> Iterator[str]: ... # pragma: no cover @overload def get( self, name: str, failobj: None = None - ) -> Optional[str]: ... # pragma: no cover + ) -> str | None: ... # pragma: no cover @overload - def get(self, name: str, failobj: _T) -> Union[str, _T]: ... # pragma: no cover + def get(self, name: str, failobj: _T) -> str | _T: ... # pragma: no cover # overload per python/importlib_metadata#435 @overload def get_all( self, name: str, failobj: None = None - ) -> Optional[List[Any]]: ... # pragma: no cover + ) -> list[Any] | None: ... # pragma: no cover @overload - def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]: + def get_all(self, name: str, failobj: _T) -> list[Any] | _T: """ Return all values associated with a possibly multi-valued key. """ @property - def json(self) -> Dict[str, Union[str, List[str]]]: + def json(self) -> dict[str, str | list[str]]: """ A JSON-compatible form of the metadata. """ @@ -50,11 +54,11 @@ class SimplePath(Protocol): """ def joinpath( - self, other: Union[str, os.PathLike[str]] + self, other: str | os.PathLike[str] ) -> SimplePath: ... # pragma: no cover def __truediv__( - self, other: Union[str, os.PathLike[str]] + self, other: str | os.PathLike[str] ) -> SimplePath: ... # pragma: no cover @property diff --git a/Lib/importlib/metadata/_typing.py b/Lib/importlib/metadata/_typing.py new file mode 100644 index 00000000000000..32b1d2b98ac987 --- /dev/null +++ b/Lib/importlib/metadata/_typing.py @@ -0,0 +1,15 @@ +import functools +import typing + +from ._meta import PackageMetadata + +md_none = functools.partial(typing.cast, PackageMetadata) +""" +Suppress type errors for optional metadata. + +Although Distribution.metadata can return None when metadata is corrupt +and thus None, allow callers to assume it's not None and crash if +that's the case. + +# python/importlib_metadata#493 +""" diff --git a/Lib/test/test_importlib/metadata/_issue138313.py b/Lib/test/test_importlib/metadata/_issue138313.py new file mode 100644 index 00000000000000..4e1c57e622d657 --- /dev/null +++ b/Lib/test/test_importlib/metadata/_issue138313.py @@ -0,0 +1,15 @@ +import os +import unittest + + +def skip_on_buildbot(func): + """ + #132947 revealed that after applying some otherwise stable + changes, only on some buildbot runners, the tests will fail with + ResourceWarnings. + """ + # detect "not github actions" as a proxy for BUILDBOT not being present yet. + is_buildbot = "GITHUB_ACTION" not in os.environ or "BUILDBOT" in os.environ + skipper = unittest.skip("Causes Resource Warnings (python/cpython#132947)") + wrapper = skipper if is_buildbot else lambda x: x + return wrapper(func) diff --git a/Lib/test/test_importlib/metadata/_path.py b/Lib/test/test_importlib/metadata/_path.py index b3cfb9cd549d6c..e63d889f96bf13 100644 --- a/Lib/test/test_importlib/metadata/_path.py +++ b/Lib/test/test_importlib/metadata/_path.py @@ -1,9 +1,14 @@ -# from jaraco.path 3.7 +# from jaraco.path 3.7.2 + +from __future__ import annotations import functools import pathlib -from typing import Dict, Protocol, Union -from typing import runtime_checkable +from collections.abc import Mapping +from typing import TYPE_CHECKING, Protocol, Union, runtime_checkable + +if TYPE_CHECKING: + from typing_extensions import Self class Symlink(str): @@ -12,29 +17,25 @@ class Symlink(str): """ -FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] # type: ignore +FilesSpec = Mapping[str, Union[str, bytes, Symlink, 'FilesSpec']] @runtime_checkable class TreeMaker(Protocol): - def __truediv__(self, *args, **kwargs): ... # pragma: no cover - - def mkdir(self, **kwargs): ... # pragma: no cover - - def write_text(self, content, **kwargs): ... # pragma: no cover - - def write_bytes(self, content): ... # pragma: no cover - - def symlink_to(self, target): ... # pragma: no cover + def __truediv__(self, other, /) -> Self: ... + def mkdir(self, *, exist_ok) -> object: ... + def write_text(self, content, /, *, encoding) -> object: ... + def write_bytes(self, content, /) -> object: ... + def symlink_to(self, target, /) -> object: ... -def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker: - return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore +def _ensure_tree_maker(obj: str | TreeMaker) -> TreeMaker: + return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) def build( spec: FilesSpec, - prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore + prefix: str | TreeMaker = pathlib.Path(), ): """ Build a set of files/directories, as described by the spec. @@ -66,23 +67,24 @@ def build( @functools.singledispatch -def create(content: Union[str, bytes, FilesSpec], path): +def create(content: str | bytes | FilesSpec, path: TreeMaker) -> None: path.mkdir(exist_ok=True) - build(content, prefix=path) # type: ignore + # Mypy only looks at the signature of the main singledispatch method. So it must contain the complete Union + build(content, prefix=path) # type: ignore[arg-type] # python/mypy#11727 @create.register -def _(content: bytes, path): +def _(content: bytes, path: TreeMaker) -> None: path.write_bytes(content) @create.register -def _(content: str, path): +def _(content: str, path: TreeMaker) -> None: path.write_text(content, encoding='utf-8') @create.register -def _(content: Symlink, path): +def _(content: Symlink, path: TreeMaker) -> None: path.symlink_to(content) diff --git a/Lib/test/test_importlib/metadata/fixtures.py b/Lib/test/test_importlib/metadata/fixtures.py index 826b1b3259b4cd..494047dc98f9b6 100644 --- a/Lib/test/test_importlib/metadata/fixtures.py +++ b/Lib/test/test_importlib/metadata/fixtures.py @@ -1,11 +1,11 @@ -import sys +import contextlib import copy +import functools import json -import shutil import pathlib +import shutil +import sys import textwrap -import functools -import contextlib from test.support import import_helper from test.support import os_helper @@ -14,14 +14,10 @@ from . import _path from ._path import FilesSpec - -try: - from importlib import resources # type: ignore - - getattr(resources, 'files') - getattr(resources, 'as_file') -except (ImportError, AttributeError): - import importlib_resources as resources # type: ignore +if sys.version_info >= (3, 9): + from importlib import resources +else: + import importlib_resources as resources @contextlib.contextmanager diff --git a/Lib/test/test_importlib/metadata/test_api.py b/Lib/test/test_importlib/metadata/test_api.py index 813febf269593b..9f6e12c87e859c 100644 --- a/Lib/test/test_importlib/metadata/test_api.py +++ b/Lib/test/test_importlib/metadata/test_api.py @@ -1,9 +1,8 @@ +import importlib import re import textwrap import unittest -import importlib -from . import fixtures from importlib.metadata import ( Distribution, PackageNotFoundError, @@ -15,6 +14,8 @@ version, ) +from . import fixtures + class APITests( fixtures.EggInfoPkg, diff --git a/Lib/test/test_importlib/metadata/test_main.py b/Lib/test/test_importlib/metadata/test_main.py index a0bc8222d5ba24..71bdd5f0d25943 100644 --- a/Lib/test/test_importlib/metadata/test_main.py +++ b/Lib/test/test_importlib/metadata/test_main.py @@ -1,8 +1,7 @@ -import re +import importlib import pickle +import re import unittest -import importlib -import importlib.metadata from test.support import os_helper try: @@ -10,8 +9,6 @@ except ImportError: from .stubs import fake_filesystem_unittest as ffs -from . import fixtures -from ._path import Symlink from importlib.metadata import ( Distribution, EntryPoint, @@ -24,6 +21,10 @@ version, ) +from . import fixtures +from . import _issue138313 +from ._path import Symlink + class BasicTests(fixtures.DistInfoPkg, unittest.TestCase): version_pattern = r'\d+\.\d+(\.\d)?' @@ -157,6 +158,16 @@ def test_valid_dists_preferred(self): dist = Distribution.from_name('foo') assert dist.version == "1.0" + def test_missing_metadata(self): + """ + Dists with a missing metadata file should return None. + + Ref python/importlib_metadata#493. + """ + fixtures.build_files(self.make_pkg('foo-4.3', files={}), self.site_dir) + assert Distribution.from_name('foo').metadata is None + assert metadata('foo') is None + class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): @staticmethod @@ -347,6 +358,7 @@ def test_packages_distributions_example(self): self._fixture_on_path('example-21.12-py3-none-any.whl') assert packages_distributions()['example'] == ['example'] + @_issue138313.skip_on_buildbot def test_packages_distributions_example2(self): """ Test packages_distributions on a wheel built diff --git a/Lib/test/test_importlib/metadata/test_zip.py b/Lib/test/test_importlib/metadata/test_zip.py index 276f6288c91598..fcb649f3736076 100644 --- a/Lib/test/test_importlib/metadata/test_zip.py +++ b/Lib/test/test_importlib/metadata/test_zip.py @@ -1,7 +1,6 @@ import sys import unittest -from . import fixtures from importlib.metadata import ( PackageNotFoundError, distribution, @@ -11,6 +10,8 @@ version, ) +from . import fixtures + class TestZip(fixtures.ZipFixtures, unittest.TestCase): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2025-08-17-10-22-31.gh-issue-132947.XR4MJ8.rst b/Misc/NEWS.d/next/Library/2025-08-17-10-22-31.gh-issue-132947.XR4MJ8.rst new file mode 100644 index 00000000000000..8a2b0a4981eae9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-17-10-22-31.gh-issue-132947.XR4MJ8.rst @@ -0,0 +1,6 @@ +Applied changes to ``importlib.metadata`` from `importlib_metadata 8.7 +`_, +including ``dist`` now disallowed for ``EntryPoints.select``; deferred +imports for faster import times; added support for metadata with newlines +(python/cpython#119650); and ``metadata()`` function now returns ``None`` +when a metadata directory is present but no metadata is present. From c9b252c2c007cd2cec3b33fc7476ae85dede8e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:45:11 +0200 Subject: [PATCH 2/6] gh-116946: Revert GC protocol for immutable empty heap types (GH-138266, GH-138288, GH-138289) (#138338) * Revert "gh-116946: fully implement GC protocol for `bz2` objects (#138266)" This reverts commit 9be91f6a20ed2fd9b491c3e779dc45c7392f60ca. * Revert "gh-116946: fully implement GC protocol for `lzma` objects (#138288)" This reverts commit 3ea16f990f81e1e3b2892f1dfd213937b1df2a68. * Revert "gh-116946: fully implement GC protocol for `_hashlib` objects (#138289)" This reverts commit 6f1dd9551a69c8c76d066a04e94db6dbc6c7597c. --- Modules/_bz2module.c | 17 +++++++------- Modules/_hashopenssl.c | 51 +++++++++--------------------------------- Modules/_lzmamodule.c | 10 ++++----- 3 files changed, 22 insertions(+), 56 deletions(-) diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c index d988901933703e..914172684158a1 100644 --- a/Modules/_bz2module.c +++ b/Modules/_bz2module.c @@ -381,14 +381,13 @@ _bz2_BZ2Compressor_impl(PyTypeObject *type, int compresslevel) static void BZ2Compressor_dealloc(PyObject *op) { - PyTypeObject *tp = Py_TYPE(op); - PyObject_GC_UnTrack(op); BZ2Compressor *self = _BZ2Compressor_CAST(op); BZ2_bzCompressEnd(&self->bzs); if (self->lock != NULL) { PyThread_free_lock(self->lock); } - tp->tp_free(self); + PyTypeObject *tp = Py_TYPE(self); + tp->tp_free((PyObject *)self); Py_DECREF(tp); } @@ -421,7 +420,7 @@ static PyType_Spec bz2_compressor_type_spec = { // bz2_compressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), .slots = bz2_compressor_type_slots, }; @@ -688,11 +687,9 @@ _bz2_BZ2Decompressor_impl(PyTypeObject *type) static void BZ2Decompressor_dealloc(PyObject *op) { - PyTypeObject *tp = Py_TYPE(op); - PyObject_GC_UnTrack(op); BZ2Decompressor *self = _BZ2Decompressor_CAST(op); - if (self->input_buffer != NULL) { + if(self->input_buffer != NULL) { PyMem_Free(self->input_buffer); } BZ2_bzDecompressEnd(&self->bzs); @@ -700,7 +697,9 @@ BZ2Decompressor_dealloc(PyObject *op) if (self->lock != NULL) { PyThread_free_lock(self->lock); } - tp->tp_free(self); + + PyTypeObject *tp = Py_TYPE(self); + tp->tp_free((PyObject *)self); Py_DECREF(tp); } @@ -752,7 +751,7 @@ static PyType_Spec bz2_decompressor_type_spec = { // bz2_decompressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), .slots = bz2_decompressor_type_slots, }; diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 9d79fc08dcfcac..a6496d0f04f2d0 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -752,9 +752,7 @@ py_wrapper_EVP_MD_CTX_new(void) static HASHobject * new_hash_object(PyTypeObject *type) { - assert(type != NULL); - assert(type->tp_alloc != NULL); - HASHobject *retval = (HASHobject *)type->tp_alloc(type, 0); + HASHobject *retval = PyObject_New(HASHobject, type); if (retval == NULL) { return NULL; } @@ -794,21 +792,13 @@ _hashlib_HASH_hash(HASHobject *self, const void *vp, Py_ssize_t len) static void _hashlib_HASH_dealloc(PyObject *op) { - PyTypeObject *tp = Py_TYPE(op); - PyObject_GC_UnTrack(op); HASHobject *self = HASHobject_CAST(op); + PyTypeObject *tp = Py_TYPE(self); EVP_MD_CTX_free(self->ctx); - tp->tp_free(self); + PyObject_Free(self); Py_DECREF(tp); } -static int -_hashlib_HASH_traverse(PyObject *op, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(op)); - return 0; -} - static int _hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p) { @@ -1003,7 +993,6 @@ PyDoc_STRVAR(HASHobject_type_doc, static PyType_Slot HASHobject_type_slots[] = { {Py_tp_dealloc, _hashlib_HASH_dealloc}, - {Py_tp_traverse, _hashlib_HASH_traverse}, {Py_tp_repr, _hashlib_HASH_repr}, {Py_tp_doc, (char *)HASHobject_type_doc}, {Py_tp_methods, HASH_methods}, @@ -1019,7 +1008,6 @@ static PyType_Spec HASHobject_type_spec = { | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE - | Py_TPFLAGS_HAVE_GC ), .slots = HASHobject_type_slots }; @@ -1177,8 +1165,6 @@ PyDoc_STRVAR(HASHXOFobject_type_doc, "digest_size -- number of bytes in this hashes output"); static PyType_Slot HASHXOFobject_type_slots[] = { - {Py_tp_dealloc, _hashlib_HASH_dealloc}, - {Py_tp_traverse, _hashlib_HASH_traverse}, {Py_tp_doc, (char *)HASHXOFobject_type_doc}, {Py_tp_methods, HASHXOFobject_methods}, {Py_tp_getset, HASHXOFobject_getsets}, @@ -1193,7 +1179,6 @@ static PyType_Spec HASHXOFobject_type_spec = { | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE - | Py_TPFLAGS_HAVE_GC ), .slots = HASHXOFobject_type_slots }; @@ -1917,8 +1902,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, goto error; } - assert(state->HMAC_type != NULL); - self = (HMACobject *)state->HMAC_type->tp_alloc(state->HMAC_type, 0); + self = PyObject_New(HMACobject, state->HMAC_type); if (self == NULL) { goto error; } @@ -2024,8 +2008,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self) return NULL; } - PyTypeObject *type = Py_TYPE(self); - retval = (HMACobject *)type->tp_alloc(type, 0); + retval = PyObject_New(HMACobject, Py_TYPE(self)); if (retval == NULL) { HMAC_CTX_free(ctx); return NULL; @@ -2039,24 +2022,16 @@ _hashlib_HMAC_copy_impl(HMACobject *self) static void _hmac_dealloc(PyObject *op) { - PyTypeObject *tp = Py_TYPE(op); - PyObject_GC_UnTrack(op); HMACobject *self = HMACobject_CAST(op); + PyTypeObject *tp = Py_TYPE(self); if (self->ctx != NULL) { HMAC_CTX_free(self->ctx); self->ctx = NULL; } - tp->tp_free(self); + PyObject_Free(self); Py_DECREF(tp); } -static int -_hashlib_HMAC_traverse(PyObject *op, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(op)); - return 0; -} - static PyObject * _hmac_repr(PyObject *op) { @@ -2223,21 +2198,15 @@ static PyType_Slot HMACtype_slots[] = { {Py_tp_doc, (char *)hmactype_doc}, {Py_tp_repr, _hmac_repr}, {Py_tp_dealloc, _hmac_dealloc}, - {Py_tp_traverse, _hashlib_HMAC_traverse}, {Py_tp_methods, HMAC_methods}, {Py_tp_getset, HMAC_getset}, {0, NULL} }; PyType_Spec HMACtype_spec = { - .name = "_hashlib.HMAC", - .basicsize = sizeof(HMACobject), - .flags = ( - Py_TPFLAGS_DEFAULT - | Py_TPFLAGS_DISALLOW_INSTANTIATION - | Py_TPFLAGS_IMMUTABLETYPE - | Py_TPFLAGS_HAVE_GC - ), + "_hashlib.HMAC", /* name */ + sizeof(HMACobject), /* basicsize */ + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE, .slots = HMACtype_slots, }; diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c index bcc9ea7a7bba68..0b0b1bc765bbc9 100644 --- a/Modules/_lzmamodule.c +++ b/Modules/_lzmamodule.c @@ -866,13 +866,12 @@ Compressor_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) static void Compressor_dealloc(PyObject *op) { - PyTypeObject *tp = Py_TYPE(op); - PyObject_GC_UnTrack(op); Compressor *self = Compressor_CAST(op); lzma_end(&self->lzs); if (self->lock != NULL) { PyThread_free_lock(self->lock); } + PyTypeObject *tp = Py_TYPE(self); tp->tp_free(self); Py_DECREF(tp); } @@ -934,7 +933,7 @@ static PyType_Spec lzma_compressor_type_spec = { // lzma_compressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), .slots = lzma_compressor_type_slots, }; @@ -1315,8 +1314,6 @@ _lzma_LZMADecompressor_impl(PyTypeObject *type, int format, static void Decompressor_dealloc(PyObject *op) { - PyTypeObject *tp = Py_TYPE(op); - PyObject_GC_UnTrack(op); Decompressor *self = Decompressor_CAST(op); if(self->input_buffer != NULL) PyMem_Free(self->input_buffer); @@ -1326,6 +1323,7 @@ Decompressor_dealloc(PyObject *op) if (self->lock != NULL) { PyThread_free_lock(self->lock); } + PyTypeObject *tp = Py_TYPE(self); tp->tp_free(self); Py_DECREF(tp); } @@ -1383,7 +1381,7 @@ static PyType_Spec lzma_decompressor_type_spec = { // lzma_decompressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), .slots = lzma_decompressor_type_slots, }; From 0d383f86eec97b343457ee1ce1d274f6b3d9ce6c Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:50:31 +0100 Subject: [PATCH 3/6] gh-54874: Expand unicodedata module documentation (#138301) Closes #54874 Co-authored-by: Alexander Belopolsky --- Doc/library/unicodedata.rst | 100 ++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/Doc/library/unicodedata.rst b/Doc/library/unicodedata.rst index 0aef597d064e0e..76f9a038181698 100644 --- a/Doc/library/unicodedata.rst +++ b/Doc/library/unicodedata.rst @@ -25,80 +25,133 @@ Standard Annex #44, `"Unicode Character Database" `_. It defines the following functions: +.. seealso:: + + The :ref:`unicode-howto` for more information about Unicode and how to use + this module. + .. function:: lookup(name) Look up character by name. If a character with the given name is found, return the corresponding character. If not found, :exc:`KeyError` is raised. + For example:: + + >>> unicodedata.lookup('LEFT CURLY BRACKET') + '{' + + The characters returned by this function are the same as those produced by + ``\N`` escape sequence in string literals. For example:: + + >>> unicodedata.lookup('MIDDLE DOT') == '\N{MIDDLE DOT}' + True .. versionchanged:: 3.3 Support for name aliases [#]_ and named sequences [#]_ has been added. -.. function:: name(chr[, default]) +.. function:: name(chr, default=None, /) Returns the name assigned to the character *chr* as a string. If no name is defined, *default* is returned, or, if not given, :exc:`ValueError` is - raised. + raised. For example:: + + >>> unicodedata.name('½') + 'VULGAR FRACTION ONE HALF' + >>> unicodedata.name('\uFFFF', 'fallback') + 'fallback' -.. function:: decimal(chr[, default]) +.. function:: decimal(chr, default=None, /) Returns the decimal value assigned to the character *chr* as integer. If no such value is defined, *default* is returned, or, if not given, - :exc:`ValueError` is raised. + :exc:`ValueError` is raised. For example:: + >>> unicodedata.decimal('\N{ARABIC-INDIC DIGIT NINE}') + 9 + >>> unicodedata.decimal('\N{SUPERSCRIPT NINE}', -1) + -1 -.. function:: digit(chr[, default]) + +.. function:: digit(chr, default=None, /) Returns the digit value assigned to the character *chr* as integer. If no such value is defined, *default* is returned, or, if not given, - :exc:`ValueError` is raised. + :exc:`ValueError` is raised:: + + >>> unicodedata.digit('\N{SUPERSCRIPT NINE}') + 9 -.. function:: numeric(chr[, default]) +.. function:: numeric(chr, default=None, /) Returns the numeric value assigned to the character *chr* as float. If no such value is defined, *default* is returned, or, if not given, - :exc:`ValueError` is raised. + :exc:`ValueError` is raised:: + + >>> unicodedata.numeric('½') + 0.5 .. function:: category(chr) Returns the general category assigned to the character *chr* as - string. + string. General category names consist of two letters. + See the `General Category Values section of the Unicode Character + Database documentation `_ + for a list of category codes. For example:: + + >>> unicodedata.category('A') # 'L'etter, 'u'ppercase + 'Lu' .. function:: bidirectional(chr) Returns the bidirectional class assigned to the character *chr* as string. If no such value is defined, an empty string is returned. + See the `Bidirectional Class Values section of the Unicode Character + Database `_ + documentation for a list of bidirectional codes. For example:: + + >>> unicodedata.bidirectional('\N{ARABIC-INDIC DIGIT SEVEN}') # 'A'rabic, 'N'umber + 'AN' .. function:: combining(chr) Returns the canonical combining class assigned to the character *chr* as integer. Returns ``0`` if no combining class is defined. + See the `Canonical Combining Class Values section of the Unicode Character + Database `_ + for more information. .. function:: east_asian_width(chr) Returns the east asian width assigned to the character *chr* as - string. + string. For a list of widths and or more information, see the + `Unicode Standard Annex #11 `_. .. function:: mirrored(chr) Returns the mirrored property assigned to the character *chr* as integer. Returns ``1`` if the character has been identified as a "mirrored" - character in bidirectional text, ``0`` otherwise. + character in bidirectional text, ``0`` otherwise. For example:: + + >>> unicodedata.mirrored('>') + 1 .. function:: decomposition(chr) Returns the character decomposition mapping assigned to the character *chr* as string. An empty string is returned in case no such mapping is - defined. + defined. For example:: + + >>> unicodedata.decomposition('Ã') + '0041 0303' .. function:: normalize(form, unistr) @@ -122,9 +175,9 @@ following functions: normally would be unified with other characters. For example, U+2160 (ROMAN NUMERAL ONE) is really the same thing as U+0049 (LATIN CAPITAL LETTER I). However, it is supported in Unicode for compatibility with existing character - sets (e.g. gb2312). + sets (for example, gb2312). - The normal form KD (NFKD) will apply the compatibility decomposition, i.e. + The normal form KD (NFKD) will apply the compatibility decomposition, that is, replace all compatibility characters with their equivalents. The normal form KC (NFKC) first applies the compatibility decomposition, followed by the canonical composition. @@ -133,6 +186,7 @@ following functions: a human reader, if one has combining characters and the other doesn't, they may not compare equal. + .. function:: is_normalized(form, unistr) Return whether the Unicode string *unistr* is in the normal form *form*. Valid @@ -154,24 +208,6 @@ In addition, the module exposes the following constant: Unicode database version 3.2 instead, for applications that require this specific version of the Unicode database (such as IDNA). -Examples: - - >>> import unicodedata - >>> unicodedata.lookup('LEFT CURLY BRACKET') - '{' - >>> unicodedata.name('/') - 'SOLIDUS' - >>> unicodedata.decimal('9') - 9 - >>> unicodedata.decimal('a') - Traceback (most recent call last): - File "", line 1, in - ValueError: not a decimal - >>> unicodedata.category('A') # 'L'etter, 'u'ppercase - 'Lu' - >>> unicodedata.bidirectional('\u0660') # 'A'rabic, 'N'umber - 'AN' - .. rubric:: Footnotes From 0d02e4d7d35b5c173a8f62342264389cb1f18601 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 1 Sep 2025 18:14:19 +0200 Subject: [PATCH 4/6] gh-116738: Fix `test_json_mutating_exact_dict` (#138339) Fix test_json_mutating_exact_dic --- Lib/test/test_free_threading/test_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_free_threading/test_json.py b/Lib/test/test_free_threading/test_json.py index 8a541e960a63ad..010eb322a15b84 100644 --- a/Lib/test/test_free_threading/test_json.py +++ b/Lib/test/test_free_threading/test_json.py @@ -56,7 +56,7 @@ def worker(barrier, data, index): if len(d) > 5: try: key = list(d)[0] - d.pop() + d.pop(key) except (KeyError, IndexError): pass else: From 4f6ecd10c2eb8da2047ebf138cf1dca3ad3d26d8 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 1 Sep 2025 12:20:33 -0400 Subject: [PATCH 5/6] gh-138342: Use a common utility for visiting an object's type (GH-138343) Add `_PyObject_VisitType` in place of `tp_traverse` functions that only visit the object's type. --- Include/cpython/object.h | 4 ++++ Modules/_dbmmodule.c | 8 +------- Modules/_decimal/_decimal.c | 18 ++---------------- Modules/_functoolsmodule.c | 9 +-------- Modules/_gdbmmodule.c | 9 +-------- Modules/_multiprocessing/semaphore.c | 9 +-------- Modules/_pickle.c | 9 +-------- Modules/_sqlite/prepare_protocol.c | 9 +-------- Modules/_sqlite/statement.c | 9 +-------- Modules/_ssl.c | 9 +-------- Modules/_threadmodule.c | 27 +++------------------------ Modules/arraymodule.c | 10 +--------- Modules/blake2module.c | 11 ++--------- Modules/hmacmodule.c | 9 +-------- Modules/md5module.c | 9 +-------- Modules/mmapmodule.c | 9 +-------- Modules/sha1module.c | 9 +-------- Modules/sha2module.c | 12 ++---------- Modules/sha3module.c | 9 +-------- Modules/socketmodule.c | 9 +-------- Modules/unicodedata.c | 9 +-------- Objects/object.c | 10 ++++++++++ Objects/typevarobject.c | 9 +-------- PC/winreg.c | 9 +-------- 24 files changed, 41 insertions(+), 203 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 973d358ed8e4ec..b244c062c7679e 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -491,3 +491,7 @@ PyAPI_FUNC(int) PyUnstable_TryIncRef(PyObject *); PyAPI_FUNC(void) PyUnstable_EnableTryIncRef(PyObject *); PyAPI_FUNC(int) PyUnstable_Object_IsUniquelyReferenced(PyObject *); + +/* Utility for the tp_traverse slot of mutable heap types that have no other + * references. */ +PyAPI_FUNC(int) _PyObject_VisitType(PyObject *op, visitproc visit, void *arg); diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c index 0cd0f043de453d..17aca2f00a13c0 100644 --- a/Modules/_dbmmodule.c +++ b/Modules/_dbmmodule.c @@ -96,12 +96,6 @@ newdbmobject(_dbm_state *state, const char *file, int flags, int mode) } /* Methods */ -static int -dbm_traverse(PyObject *dp, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(dp)); - return 0; -} static void dbm_dealloc(PyObject *self) @@ -540,7 +534,7 @@ static PyMethodDef dbm_methods[] = { static PyType_Slot dbmtype_spec_slots[] = { {Py_tp_dealloc, dbm_dealloc}, - {Py_tp_traverse, dbm_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_methods, dbm_methods}, {Py_sq_contains, dbm_contains}, {Py_mp_length, dbm_length}, diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index de7200af8c158b..50994be2968d35 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -746,13 +746,6 @@ signaldict_setitem(PyObject *self, PyObject *key, PyObject *value) return 0; } -static int -signaldict_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void signaldict_dealloc(PyObject *self) { @@ -845,7 +838,7 @@ static PyMethodDef signaldict_methods[] = { static PyType_Slot signaldict_slots[] = { {Py_tp_dealloc, signaldict_dealloc}, - {Py_tp_traverse, signaldict_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_repr, signaldict_repr}, {Py_tp_hash, PyObject_HashNotImplemented}, {Py_tp_getattro, PyObject_GenericGetAttr}, @@ -2194,13 +2187,6 @@ PyDecType_New(decimal_state *state, PyTypeObject *type) } #define dec_alloc(st) PyDecType_New(st, (st)->PyDec_Type) -static int -dec_traverse(PyObject *dec, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(dec)); - return 0; -} - static void dec_dealloc(PyObject *dec) { @@ -6032,7 +6018,7 @@ static PyType_Slot dec_slots[] = { {Py_tp_token, Py_TP_USE_SPEC}, {Py_tp_dealloc, dec_dealloc}, {Py_tp_getattro, PyObject_GenericGetAttr}, - {Py_tp_traverse, dec_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_repr, dec_repr}, {Py_tp_hash, dec_hash}, {Py_tp_str, dec_str}, diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index f077a0ed329516..257d5c6d53611c 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -108,20 +108,13 @@ placeholder_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) return placeholder; } -static int -placeholder_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static PyType_Slot placeholder_type_slots[] = { {Py_tp_dealloc, placeholder_dealloc}, {Py_tp_repr, placeholder_repr}, {Py_tp_doc, (void *)placeholder_doc}, {Py_tp_methods, placeholder_methods}, {Py_tp_new, placeholder_new}, - {Py_tp_traverse, placeholder_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, 0} }; diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c index 76072ca60cf6cd..7bef6ae7f0c43e 100644 --- a/Modules/_gdbmmodule.c +++ b/Modules/_gdbmmodule.c @@ -122,13 +122,6 @@ newgdbmobject(_gdbm_state *state, const char *file, int flags, int mode) } /* Methods */ -static int -gdbm_traverse(PyObject *op, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(op)); - return 0; -} - static void gdbm_dealloc(PyObject *op) { @@ -714,7 +707,7 @@ static PyMethodDef gdbm_methods[] = { static PyType_Slot gdbmtype_spec_slots[] = { {Py_tp_dealloc, gdbm_dealloc}, - {Py_tp_traverse, gdbm_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_methods, gdbm_methods}, {Py_sq_contains, gdbm_contains}, {Py_mp_length, gdbm_length}, diff --git a/Modules/_multiprocessing/semaphore.c b/Modules/_multiprocessing/semaphore.c index a4a2a866ccbfce..d5a1f27e9ff4ff 100644 --- a/Modules/_multiprocessing/semaphore.c +++ b/Modules/_multiprocessing/semaphore.c @@ -720,13 +720,6 @@ _multiprocessing_SemLock___exit___impl(SemLockObject *self, return _multiprocessing_SemLock_release_impl(self); } -static int -semlock_traverse(PyObject *s, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(s)); - return 0; -} - /* * Semaphore methods */ @@ -773,7 +766,7 @@ static PyType_Slot _PyMp_SemLockType_slots[] = { {Py_tp_members, semlock_members}, {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_new, _multiprocessing_SemLock}, - {Py_tp_traverse, semlock_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_free, PyObject_GC_Del}, {Py_tp_doc, (void *)PyDoc_STR("Semaphore/Mutex type")}, {0, 0}, diff --git a/Modules/_pickle.c b/Modules/_pickle.c index bc06478799345a..0774e91039ec41 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -413,13 +413,6 @@ typedef struct { #define Pdata_CAST(op) ((Pdata *)(op)) -static int -Pdata_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void Pdata_dealloc(PyObject *op) { @@ -437,7 +430,7 @@ Pdata_dealloc(PyObject *op) static PyType_Slot pdata_slots[] = { {Py_tp_dealloc, Pdata_dealloc}, - {Py_tp_traverse, Pdata_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, NULL}, }; diff --git a/Modules/_sqlite/prepare_protocol.c b/Modules/_sqlite/prepare_protocol.c index 31092417cb480d..d7ac09e3947a77 100644 --- a/Modules/_sqlite/prepare_protocol.c +++ b/Modules/_sqlite/prepare_protocol.c @@ -29,13 +29,6 @@ pysqlite_prepare_protocol_init(PyObject *self, PyObject *args, PyObject *kwargs) return 0; } -static int -pysqlite_prepare_protocol_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void pysqlite_prepare_protocol_dealloc(PyObject *self) { @@ -50,7 +43,7 @@ PyDoc_STRVAR(doc, "PEP 246 style object adaption protocol type."); static PyType_Slot type_slots[] = { {Py_tp_dealloc, pysqlite_prepare_protocol_dealloc}, {Py_tp_init, pysqlite_prepare_protocol_init}, - {Py_tp_traverse, pysqlite_prepare_protocol_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_doc, (void *)doc}, {0, NULL}, }; diff --git a/Modules/_sqlite/statement.c b/Modules/_sqlite/statement.c index 736e60fd778287..77181104eda10b 100644 --- a/Modules/_sqlite/statement.c +++ b/Modules/_sqlite/statement.c @@ -116,13 +116,6 @@ stmt_dealloc(PyObject *op) Py_DECREF(tp); } -static int -stmt_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - /* * Strip leading whitespace and comments from incoming SQL (null terminated C * string) and return a pointer to the first non-whitespace, non-comment @@ -183,7 +176,7 @@ lstrip_sql(const char *sql) static PyType_Slot stmt_slots[] = { {Py_tp_dealloc, stmt_dealloc}, - {Py_tp_traverse, stmt_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, NULL}, }; diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 2388bbb3bae631..048e640283638b 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -5692,13 +5692,6 @@ _ssl_MemoryBIO_impl(PyTypeObject *type) return (PyObject *) self; } -static int -memory_bio_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void memory_bio_dealloc(PyObject *op) { @@ -5869,7 +5862,7 @@ static PyType_Slot PySSLMemoryBIO_slots[] = { {Py_tp_getset, memory_bio_getsetlist}, {Py_tp_new, _ssl_MemoryBIO}, {Py_tp_dealloc, memory_bio_dealloc}, - {Py_tp_traverse, memory_bio_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, 0}, }; diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 0a22907375b0dd..1a64289ea01fdb 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -655,13 +655,6 @@ PyThreadHandleObject_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return (PyObject *)PyThreadHandleObject_new(type); } -static int -PyThreadHandleObject_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void PyThreadHandleObject_dealloc(PyObject *op) { @@ -751,7 +744,7 @@ static PyType_Slot ThreadHandle_Type_slots[] = { {Py_tp_dealloc, PyThreadHandleObject_dealloc}, {Py_tp_repr, PyThreadHandleObject_repr}, {Py_tp_getset, ThreadHandle_getsetlist}, - {Py_tp_traverse, PyThreadHandleObject_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_methods, ThreadHandle_methods}, {Py_tp_new, PyThreadHandleObject_tp_new}, {0, 0} @@ -767,13 +760,6 @@ static PyType_Spec ThreadHandle_Type_spec = { /* Lock objects */ -static int -lock_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void lock_dealloc(PyObject *self) { @@ -1045,7 +1031,7 @@ static PyType_Slot lock_type_slots[] = { {Py_tp_repr, lock_repr}, {Py_tp_doc, (void *)lock_doc}, {Py_tp_methods, lock_methods}, - {Py_tp_traverse, lock_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_new, lock_new}, {0, 0} }; @@ -1060,13 +1046,6 @@ static PyType_Spec lock_type_spec = { /* Recursive lock objects */ -static int -rlock_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static int rlock_locked_impl(rlockobject *self) { @@ -1359,7 +1338,7 @@ static PyType_Slot rlock_type_slots[] = { {Py_tp_methods, rlock_methods}, {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_new, rlock_new}, - {Py_tp_traverse, rlock_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, 0}, }; diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 654e9445985d84..d9ac5b97f8258f 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -714,14 +714,6 @@ ins1(arrayobject *self, Py_ssize_t where, PyObject *v) } /* Methods */ - -static int -array_tp_traverse(PyObject *op, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(op)); - return 0; -} - static void array_dealloc(PyObject *op) { @@ -2968,7 +2960,7 @@ static PyType_Slot array_slots[] = { {Py_tp_getset, array_getsets}, {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_new, array_new}, - {Py_tp_traverse, array_tp_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, /* as sequence */ {Py_sq_length, array_length}, diff --git a/Modules/blake2module.c b/Modules/blake2module.c index 163f238a4268d0..4921e8f945ef37 100644 --- a/Modules/blake2module.c +++ b/Modules/blake2module.c @@ -1008,17 +1008,10 @@ py_blake2_dealloc(PyObject *self) Py_DECREF(type); } -static int -py_blake2_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static PyType_Slot blake2b_type_slots[] = { {Py_tp_clear, py_blake2_clear}, {Py_tp_dealloc, py_blake2_dealloc}, - {Py_tp_traverse, py_blake2_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_doc, (char *)py_blake2b_new__doc__}, {Py_tp_methods, py_blake2b_methods}, {Py_tp_getset, py_blake2b_getsetters}, @@ -1029,7 +1022,7 @@ static PyType_Slot blake2b_type_slots[] = { static PyType_Slot blake2s_type_slots[] = { {Py_tp_clear, py_blake2_clear}, {Py_tp_dealloc, py_blake2_dealloc}, - {Py_tp_traverse, py_blake2_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_doc, (char *)py_blake2s_new__doc__}, {Py_tp_methods, py_blake2b_methods}, {Py_tp_getset, py_blake2b_getsetters}, diff --git a/Modules/hmacmodule.c b/Modules/hmacmodule.c index 680c93a04cac17..b30dda501a0716 100644 --- a/Modules/hmacmodule.c +++ b/Modules/hmacmodule.c @@ -1032,13 +1032,6 @@ HMACObject_dealloc(PyObject *op) Py_DECREF(type); } -static int -HMACObject_traverse(PyObject *op, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(op)); - return 0; -} - static PyMethodDef HMACObject_methods[] = { _HMAC_HMAC_COPY_METHODDEF _HMAC_HMAC_UPDATE_METHODDEF @@ -1060,7 +1053,7 @@ static PyType_Slot HMACObject_Type_slots[] = { {Py_tp_getset, HMACObject_getsets}, {Py_tp_clear, HMACObject_clear}, {Py_tp_dealloc, HMACObject_dealloc}, - {Py_tp_traverse, HMACObject_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, NULL} /* sentinel */ }; diff --git a/Modules/md5module.c b/Modules/md5module.c index 8b6dd4a8195dfb..6b6457427b6b5e 100644 --- a/Modules/md5module.c +++ b/Modules/md5module.c @@ -82,13 +82,6 @@ newMD5object(MD5State * st) } /* Internal methods for a hash object */ -static int -MD5_traverse(PyObject *ptr, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(ptr)); - return 0; -} - static void MD5_dealloc(PyObject *op) { @@ -246,7 +239,7 @@ static PyType_Slot md5_type_slots[] = { {Py_tp_dealloc, MD5_dealloc}, {Py_tp_methods, MD5_methods}, {Py_tp_getset, MD5_getseters}, - {Py_tp_traverse, MD5_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0,0} }; diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 0cb4b62d734550..8413ebe668dffe 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -128,13 +128,6 @@ typedef struct { #define mmap_object_CAST(op) ((mmap_object *)(op)) -static int -mmap_object_traverse(PyObject *op, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(op)); - return 0; -} - static void mmap_object_dealloc(PyObject *op) { @@ -1499,7 +1492,7 @@ static PyType_Slot mmap_object_slots[] = { {Py_tp_members, mmap_object_members}, {Py_tp_getset, mmap_object_getset}, {Py_tp_getattro, PyObject_GenericGetAttr}, - {Py_tp_traverse, mmap_object_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, /* as sequence */ {Py_sq_length, mmap_length}, diff --git a/Modules/sha1module.c b/Modules/sha1module.c index faa9dcccc5755b..d64eb91458cae8 100644 --- a/Modules/sha1module.c +++ b/Modules/sha1module.c @@ -81,13 +81,6 @@ newSHA1object(SHA1State *st) /* Internal methods for a hash object */ -static int -SHA1_traverse(PyObject *ptr, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(ptr)); - return 0; -} - static void SHA1_dealloc(PyObject *op) { @@ -247,7 +240,7 @@ static PyType_Slot sha1_type_slots[] = { {Py_tp_dealloc, SHA1_dealloc}, {Py_tp_methods, SHA1_methods}, {Py_tp_getset, SHA1_getseters}, - {Py_tp_traverse, SHA1_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0,0} }; diff --git a/Modules/sha2module.c b/Modules/sha2module.c index 36300ba899fd44..66259fe47a0fd3 100644 --- a/Modules/sha2module.c +++ b/Modules/sha2module.c @@ -164,14 +164,6 @@ newSHA512object(sha2_state *state) } /* Internal methods for our hash objects. */ - -static int -SHA2_traverse(PyObject *ptr, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(ptr)); - return 0; -} - static void SHA256_dealloc(PyObject *op) { @@ -519,7 +511,7 @@ static PyType_Slot sha256_types_slots[] = { {Py_tp_dealloc, SHA256_dealloc}, {Py_tp_methods, SHA256_methods}, {Py_tp_getset, SHA256_getseters}, - {Py_tp_traverse, SHA2_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0,0} }; @@ -527,7 +519,7 @@ static PyType_Slot sha512_type_slots[] = { {Py_tp_dealloc, SHA512_dealloc}, {Py_tp_methods, SHA512_methods}, {Py_tp_getset, SHA512_getseters}, - {Py_tp_traverse, SHA2_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0,0} }; diff --git a/Modules/sha3module.c b/Modules/sha3module.c index 5764556bb680f3..de4bf09b8e7e0b 100644 --- a/Modules/sha3module.c +++ b/Modules/sha3module.c @@ -226,13 +226,6 @@ SHA3_dealloc(PyObject *self) Py_DECREF(tp); } -static int -SHA3_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - /* External methods for a hash object */ @@ -424,7 +417,7 @@ static PyGetSetDef SHA3_getseters[] = { static PyType_Slot type_slots_obj[] = { \ {Py_tp_clear, SHA3_clear}, \ {Py_tp_dealloc, SHA3_dealloc}, \ - {Py_tp_traverse, SHA3_traverse}, \ + {Py_tp_traverse, _PyObject_VisitType}, \ {Py_tp_doc, (char*)type_doc}, \ {Py_tp_methods, type_methods}, \ {Py_tp_getset, type_getseters}, \ diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index c5b16dc4fe4c5f..dc8e08ac3e522e 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -5538,13 +5538,6 @@ sock_finalize(PyObject *self) PyErr_SetRaisedException(exc); } -static int -sock_traverse(PyObject *s, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(s)); - return 0; -} - static void sock_dealloc(PyObject *s) { @@ -5843,7 +5836,7 @@ sock_initobj_impl(PySocketSockObject *self, int family, int type, int proto, static PyType_Slot sock_slots[] = { {Py_tp_dealloc, sock_dealloc}, - {Py_tp_traverse, sock_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_repr, sock_repr}, {Py_tp_doc, (void *)sock_doc}, {Py_tp_methods, sock_methods}, diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c index 2a30030a2a1153..41725e5aec1641 100644 --- a/Modules/unicodedata.c +++ b/Modules/unicodedata.c @@ -1594,13 +1594,6 @@ static PyMethodDef unicodedata_functions[] = { {NULL, NULL} /* sentinel */ }; -static int -ucd_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void ucd_dealloc(PyObject *self) { @@ -1612,7 +1605,7 @@ ucd_dealloc(PyObject *self) static PyType_Slot ucd_type_slots[] = { {Py_tp_dealloc, ucd_dealloc}, - {Py_tp_traverse, ucd_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_getattro, PyObject_GenericGetAttr}, {Py_tp_methods, unicodedata_functions}, {Py_tp_members, DB_members}, diff --git a/Objects/object.c b/Objects/object.c index fba86e63cd4a11..bd3ba02f8eb255 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -3384,3 +3384,13 @@ PyUnstable_Object_IsUniquelyReferenced(PyObject *op) assert(op != NULL); return _PyObject_IsUniquelyReferenced(op); } + +int +_PyObject_VisitType(PyObject *op, visitproc visit, void *arg) +{ + assert(op != NULL); + PyTypeObject *tp = Py_TYPE(op); + _PyObject_ASSERT((PyObject *)tp, PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)); + Py_VISIT(tp); + return 0; +} diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index cead6e69af5451..522e9fd9c955af 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -2304,20 +2304,13 @@ generic_dealloc(PyObject *self) Py_DECREF(tp); } -static int -generic_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static PyType_Slot generic_slots[] = { {Py_tp_doc, (void *)generic_doc}, {Py_tp_methods, generic_methods}, {Py_tp_dealloc, generic_dealloc}, {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, - {Py_tp_traverse, generic_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, NULL}, }; diff --git a/PC/winreg.c b/PC/winreg.c index 05a33006c32326..9bbacb0f50bd63 100644 --- a/PC/winreg.c +++ b/PC/winreg.c @@ -161,13 +161,6 @@ PyHKEY_deallocFunc(PyObject *ob) Py_DECREF(tp); } -static int -PyHKEY_traverseFunc(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static int PyHKEY_boolFunc(PyObject *ob) { @@ -369,7 +362,7 @@ static PyType_Slot pyhkey_type_slots[] = { {Py_tp_members, PyHKEY_memberlist}, {Py_tp_methods, PyHKEY_methods}, {Py_tp_doc, (char *)PyHKEY_doc}, - {Py_tp_traverse, PyHKEY_traverseFunc}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_hash, PyHKEY_hashFunc}, {Py_tp_str, PyHKEY_strFunc}, From 6528cf47a0c7747cce659cfe5b94a74b17aa22c2 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 1 Sep 2025 18:58:45 +0200 Subject: [PATCH 6/6] gh-90548: Fix musl version detection with --strip-all (#137864) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Lib/platform.py | 12 +++++++++--- Lib/test/test_platform.py | 6 ++++++ .../2025-08-16-18-11-41.gh-issue-90548.q3aJUK.rst | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-08-16-18-11-41.gh-issue-90548.q3aJUK.rst diff --git a/Lib/platform.py b/Lib/platform.py index 762f5d06099cf0..4028012dc3ce6a 100644 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -199,6 +199,7 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): | (GLIBC_([0-9.]+)) | (libc(_\w+)?\.so(?:\.(\d[0-9.]*))?) | (musl-([0-9.]+)) + | (libc.musl(?:-\w+)?.so(?:\.(\d[0-9.]*))?) """, re.ASCII | re.VERBOSE) @@ -224,9 +225,10 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): continue if not m: break - libcinit, glibc, glibcversion, so, threads, soversion, musl, muslversion = [ - s.decode('latin1') if s is not None else s - for s in m.groups()] + decoded_groups = [s.decode('latin1') if s is not None else s + for s in m.groups()] + (libcinit, glibc, glibcversion, so, threads, soversion, + musl, muslversion, musl_so, musl_sover) = decoded_groups if libcinit and not lib: lib = 'libc' elif glibc: @@ -246,6 +248,10 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): lib = 'musl' if not ver or V(muslversion) > V(ver): ver = muslversion + elif musl_so: + lib = 'musl' + if musl_sover and (not ver or V(musl_sover) > V(ver)): + ver = musl_sover pos = m.end() return lib, version if ver is None else ver diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 73d4f3cbdb689d..601d450b7de9eb 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -567,6 +567,8 @@ def test_libc_ver(self): # musl uses semver, but we accept some variations anyway: (b'/aports/main/musl/src/musl-12.5', ('musl', '12.5')), (b'/aports/main/musl/src/musl-1.2.5.7', ('musl', '1.2.5.7')), + (b'libc.musl.so.1', ('musl', '1')), + (b'libc.musl-x86_64.so.1.2.5', ('musl', '1.2.5')), (b'', ('', '')), ): with open(filename, 'wb') as fp: @@ -585,6 +587,10 @@ def test_libc_ver(self): (b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0', ('glibc', '1.23.4')), (b'libc.so.2.4\0libc.so.9\0libc.so.23.1\0', ('libc', '23.1')), (b'musl-1.4.1\0musl-2.1.1\0musl-2.0.1\0', ('musl', '2.1.1')), + ( + b'libc.musl-x86_64.so.1.4.1\0libc.musl-x86_64.so.2.1.1\0libc.musl-x86_64.so.2.0.1', + ('musl', '2.1.1'), + ), (b'no match here, so defaults are used', ('test', '100.1.0')), ): with open(filename, 'wb') as f: diff --git a/Misc/NEWS.d/next/Library/2025-08-16-18-11-41.gh-issue-90548.q3aJUK.rst b/Misc/NEWS.d/next/Library/2025-08-16-18-11-41.gh-issue-90548.q3aJUK.rst new file mode 100644 index 00000000000000..f6e24af30fec1c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-16-18-11-41.gh-issue-90548.q3aJUK.rst @@ -0,0 +1,2 @@ +Fix ``musl`` detection for :func:`platform.libc_ver` on Alpine Linux if +compiled with --strip-all.