From ae7fc4a4f6b711173bb70d23755ec15b8ac958a6 Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Tue, 24 Feb 2026 00:51:03 +0100 Subject: [PATCH 1/2] gh-110937: Document full public importlib.metadata.Distribution API (#143480) --- Doc/library/importlib.metadata.rst | 142 +++++++++++++----- Lib/importlib/metadata/__init__.py | 1 + ...-01-06-16-04-08.gh-issue-110937.SyO5lk.rst | 1 + 3 files changed, 104 insertions(+), 40 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2026-01-06-16-04-08.gh-issue-110937.SyO5lk.rst diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 9e08e5aa989cf7..cc426326b29932 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -125,8 +125,8 @@ Entry points :meth:`!select` method for comparison to the attributes of the individual entry point definitions. - Note: it is not currently possible to query for entry points based on - their :attr:`!EntryPoint.dist` attribute (as different :class:`!Distribution` + Note: to query for entry points based on :attr:`!EntryPoint.dist` attribute, + use :meth:`Distribution.entry_points` instead (as different :class:`Distribution` instances do not currently compare equal, even if they have the same attributes) .. class:: EntryPoints @@ -291,7 +291,7 @@ Distribution files .. function:: files(distribution_name) Return the full set of files contained within the named - distribution package. + distribution package as :class:`PackagePath` instances. Raises :exc:`PackageNotFoundError` if the named distribution package is not installed in the current Python environment. @@ -304,12 +304,22 @@ Distribution files A :class:`pathlib.PurePath` derived object with additional ``dist``, ``size``, and ``hash`` properties corresponding to the distribution - package's installation metadata for that file. + package's installation metadata for that file, also: + + .. method:: locate() + + If possible, return the concrete :class:`SimplePath` allowing to access data, + or raise a :exc:`NotImplementedError` otherwise. + +.. class:: SimplePath + + A protocol representing a minimal subset of :class:`pathlib.Path` that allows to + check if it ``exists()``, to traverse using ``joinpath()`` and ``parent``, + and to retrieve data using ``read_text()`` and ``read_bytes()``. The :func:`!files` function takes a `Distribution Package `_ -name and returns all of the files installed by this distribution. Each file is reported -as a :class:`PackagePath` instance. For example:: +name and returns all of the files installed by this distribution. For example:: >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0] # doctest: +SKIP >>> util # doctest: +SKIP @@ -402,6 +412,18 @@ function is not reliable with such installs. Distributions ============= +While the module level API described above is the most common and convenient usage, +all that information is accessible from the :class:`Distribution` class. +:class:`!Distribution` is an abstract object that represents the metadata for +a Python `Distribution Package `_. +Get the concrete :class:`!Distribution` subclass instance for an installed +distribution package by calling the :func:`distribution` function:: + + >>> from importlib.metadata import distribution # doctest: +SKIP + >>> dist = distribution('wheel') # doctest: +SKIP + >>> type(dist) # doctest: +SKIP + + .. function:: distribution(distribution_name) Return a :class:`Distribution` instance describing the named @@ -410,6 +432,14 @@ Distributions Raises :exc:`PackageNotFoundError` if the named distribution package is not installed in the current Python environment. +Thus, an alternative way to get e.g. the version number is through the +:attr:`Distribution.version` attribute:: + + >>> dist.version # doctest: +SKIP + '0.32.3' + +The same applies for :func:`entry_points` and :func:`files`. + .. class:: Distribution Details of an installed distribution package. @@ -418,53 +448,85 @@ Distributions equal, even if they relate to the same installed distribution and accordingly have the same attributes. - .. method:: discover(cls, *, context=None, **kwargs) + .. staticmethod:: at(path) + .. classmethod:: from_name(name) + + Return a :class:`!Distribution` instance at the given path or + with the given name. - Returns an iterable of :class:`Distribution` instances for all packages. + .. classmethod:: discover(*, context=None, **kwargs) + + Returns an iterable of :class:`!Distribution` instances for all packages + (see distribution-discovery_). The optional argument *context* is a :class:`DistributionFinder.Context` instance, used to modify the search for distributions. Alternatively, *kwargs* may contain keyword arguments for constructing a new :class:`!DistributionFinder.Context`. + .. attribute:: metadata + :type: PackageMetadata -While the module level API described above is the most common and convenient usage, -you can get all of that information from the :class:`!Distribution` class. -:class:`!Distribution` is an abstract object that represents the metadata for -a Python `Distribution Package `_. -You can get the concrete :class:`!Distribution` subclass instance for an installed -distribution package by calling the :func:`distribution` function:: + There are all kinds of additional metadata available on :class:`!Distribution` + instances as a :class:`PackageMetadata` instance:: - >>> from importlib.metadata import distribution # doctest: +SKIP - >>> dist = distribution('wheel') # doctest: +SKIP - >>> type(dist) # doctest: +SKIP - + >>> dist.metadata['Requires-Python'] # doctest: +SKIP + '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' + >>> dist.metadata['License'] # doctest: +SKIP + 'MIT' -Thus, an alternative way to get the version number is through the -:class:`!Distribution` instance:: + The full set of available metadata is not described here. + See the PyPA `Core metadata specification `_ for additional details. - >>> dist.version # doctest: +SKIP - '0.32.3' + .. attribute:: name + :type: str + .. attribute:: requires + :type: list[str] + .. attribute:: version + :type: str -There are all kinds of additional metadata available on :class:`!Distribution` -instances:: + A few metadata fields are also available as shortcut properties. - >>> dist.metadata['Requires-Python'] # doctest: +SKIP - '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' - >>> dist.metadata['License'] # doctest: +SKIP - 'MIT' + .. versionadded:: 3.10 -For editable packages, an ``origin`` property may present :pep:`610` -metadata:: + The ``name`` shortcut was added. - >>> dist.origin.url - 'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl' + .. attribute:: origin -The full set of available metadata is not described here. -See the PyPA `Core metadata specification `_ for additional details. + For editable packages, an ``origin`` property may present :pep:`610` + metadata (for non-editable packages, ``origin`` is :const:`None`):: + + >>> dist.origin.url + 'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl' + + The ``origin`` object follows the `Direct URL Data Structure + `_. + + .. versionadded:: 3.13 + + .. attribute:: entry_points + :type: EntryPoints + + The entry points provided by this distribution package. + + .. attribute:: files + :type: list[PackagePath] | None + + All files contained in this distribution package. + Like :func:`files`, this returns :const:`None` if there are no records. + + The following two abstract methods need to be implemented when implementing-custom-providers_: + + .. method:: locate_file(path) + + Like :meth:`!PackagePath.locate`, return a :class:`SimplePath` for the given path. + Takes a :class:`os.PathLike` or a :class:`str`. + + .. method:: read_text(filename) + + A shortcut for ``distribution.locate_file(filename).read_text()``. -.. versionadded:: 3.13 - The ``.origin`` property was added. +.. _distribution-discovery: Distribution Discovery ====================== @@ -575,8 +637,8 @@ consumer. In practice, to support finding distribution package metadata in locations other than the file system, subclass -``Distribution`` and implement the abstract methods. Then from -a custom finder, return instances of this derived ``Distribution`` in the +:class:`!Distribution` and implement the abstract methods. Then from +a custom finder, return instances of this derived :class:`!Distribution` in the ``find_distributions()`` method. Example @@ -653,8 +715,8 @@ packages served by the ``DatabaseImporter``, assuming that the ``.entry_points`` attributes. The ``DatabaseDistribution`` may also provide other metadata files, like -``RECORD`` (required for ``Distribution.files``) or override the -implementation of ``Distribution.files``. See the source for more inspiration. +``RECORD`` (required for :attr:`!Distribution.files`) or override the +implementation of :attr:`!Distribution.files`. See the source for more inspiration. .. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index 7cf4d29d330c91..e91acc065ba9ae 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -41,6 +41,7 @@ 'DistributionFinder', 'PackageMetadata', 'PackageNotFoundError', + 'PackagePath', 'SimplePath', 'distribution', 'distributions', diff --git a/Misc/NEWS.d/next/Documentation/2026-01-06-16-04-08.gh-issue-110937.SyO5lk.rst b/Misc/NEWS.d/next/Documentation/2026-01-06-16-04-08.gh-issue-110937.SyO5lk.rst new file mode 100644 index 00000000000000..d29bde5ca690c6 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2026-01-06-16-04-08.gh-issue-110937.SyO5lk.rst @@ -0,0 +1 @@ +Document rest of full public :class:`importlib.metadata.Distribution` API. Also add the (already documented) :class:`~importlib.metadata.PackagePath` to ``__all__``. From fd0400585eb957c7d10812d87a8cb9e1f3c72519 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:53:17 +0000 Subject: [PATCH 2/2] `_struct.c`: Fix UB from integer overflow in `prepare_s` (GH-145158) Avoid possible undefined behaviour from signed overflow in `struct` module As discovered via oss-fuzz. --- Lib/test/test_struct.py | 3 +++ .../2026-02-23-20-52-55.gh-issue-145158.vWJtxI.rst | 2 ++ Modules/_struct.c | 10 +++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-02-23-20-52-55.gh-issue-145158.vWJtxI.rst diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index bffbcb1a60757d..aa793a2c223de9 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -552,6 +552,9 @@ def test_count_overflow(self): hugecount2 = '{}b{}H'.format(sys.maxsize//2, sys.maxsize//2) self.assertRaises(struct.error, struct.calcsize, hugecount2) + hugecount3 = '{}i{}q'.format(sys.maxsize // 4, sys.maxsize // 8) + self.assertRaises(struct.error, struct.calcsize, hugecount3) + def test_trailing_counter(self): store = array.array('b', b' '*100) diff --git a/Misc/NEWS.d/next/Library/2026-02-23-20-52-55.gh-issue-145158.vWJtxI.rst b/Misc/NEWS.d/next/Library/2026-02-23-20-52-55.gh-issue-145158.vWJtxI.rst new file mode 100644 index 00000000000000..60a5e4ad1f0ca4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-23-20-52-55.gh-issue-145158.vWJtxI.rst @@ -0,0 +1,2 @@ +Avoid undefined behaviour from signed integer overflow when parsing format +strings in the :mod:`struct` module. diff --git a/Modules/_struct.c b/Modules/_struct.c index 7d2dfc591a2a58..ae8a8ffb3c005a 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1678,7 +1678,15 @@ prepare_s(PyStructObject *self) case 's': _Py_FALLTHROUGH; case 'p': len++; ncodes++; break; case 'x': break; - default: len += num; if (num) ncodes++; break; + default: + if (num > PY_SSIZE_T_MAX - len) { + goto overflow; + } + len += num; + if (num) { + ncodes++; + } + break; } itemsize = e->size;