https://github.com/python/cpython/commit/99edd54fa7f2765cc4ad1d3988bbda5f4d726b08
commit: 99edd54fa7f2765cc4ad1d3988bbda5f4d726b08
branch: main
author: Petr Viktorin <[email protected]>
committer: encukou <[email protected]>
date: 2026-10-21T23:01:11+02:00
summary:

gh-156870: Add common Stable ABI notes for tables of definitions (GH-156871)

files:
M Doc/c-api/exceptions.rst
M Doc/c-api/iterator.rst
M Doc/tools/extensions/c_annotations.py

diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index 7f4e1ecc2aba26d..55b7e36b9b680e5 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -1130,125 +1130,192 @@ For completeness, here are all the variables:
 Exception types
 ---------------
 
-.. list-table::
-   :align: left
-   :widths: auto
-   :header-rows: 1
-
-   * * C name
-     * Python name
-   * * .. c:var:: PyObject *PyExc_BaseException
-     * :exc:`BaseException`
-   * * .. c:var:: PyObject *PyExc_BaseExceptionGroup
-     * :exc:`BaseExceptionGroup`
-   * * .. c:var:: PyObject *PyExc_Exception
-     * :exc:`Exception`
-   * * .. c:var:: PyObject *PyExc_ArithmeticError
-     * :exc:`ArithmeticError`
-   * * .. c:var:: PyObject *PyExc_AssertionError
-     * :exc:`AssertionError`
-   * * .. c:var:: PyObject *PyExc_AttributeError
-     * :exc:`AttributeError`
-   * * .. c:var:: PyObject *PyExc_BlockingIOError
-     * :exc:`BlockingIOError`
-   * * .. c:var:: PyObject *PyExc_BrokenPipeError
-     * :exc:`BrokenPipeError`
-   * * .. c:var:: PyObject *PyExc_BufferError
-     * :exc:`BufferError`
-   * * .. c:var:: PyObject *PyExc_ChildProcessError
-     * :exc:`ChildProcessError`
-   * * .. c:var:: PyObject *PyExc_ConnectionAbortedError
-     * :exc:`ConnectionAbortedError`
-   * * .. c:var:: PyObject *PyExc_ConnectionError
-     * :exc:`ConnectionError`
-   * * .. c:var:: PyObject *PyExc_ConnectionRefusedError
-     * :exc:`ConnectionRefusedError`
-   * * .. c:var:: PyObject *PyExc_ConnectionResetError
-     * :exc:`ConnectionResetError`
-   * * .. c:var:: PyObject *PyExc_EOFError
-     * :exc:`EOFError`
-   * * .. c:var:: PyObject *PyExc_FileExistsError
-     * :exc:`FileExistsError`
-   * * .. c:var:: PyObject *PyExc_FileNotFoundError
-     * :exc:`FileNotFoundError`
-   * * .. c:var:: PyObject *PyExc_FloatingPointError
-     * :exc:`FloatingPointError`
-   * * .. c:var:: PyObject *PyExc_GeneratorExit
-     * :exc:`GeneratorExit`
-   * * .. c:var:: PyObject *PyExc_ImportCycleError
-     * :exc:`ImportCycleError`
-   * * .. c:var:: PyObject *PyExc_ImportError
-     * :exc:`ImportError`
-   * * .. c:var:: PyObject *PyExc_IndentationError
-     * :exc:`IndentationError`
-   * * .. c:var:: PyObject *PyExc_IndexError
-     * :exc:`IndexError`
-   * * .. c:var:: PyObject *PyExc_InterruptedError
-     * :exc:`InterruptedError`
-   * * .. c:var:: PyObject *PyExc_IsADirectoryError
-     * :exc:`IsADirectoryError`
-   * * .. c:var:: PyObject *PyExc_KeyError
-     * :exc:`KeyError`
-   * * .. c:var:: PyObject *PyExc_KeyboardInterrupt
-     * :exc:`KeyboardInterrupt`
-   * * .. c:var:: PyObject *PyExc_LookupError
-     * :exc:`LookupError`
-   * * .. c:var:: PyObject *PyExc_MemoryError
-     * :exc:`MemoryError`
-   * * .. c:var:: PyObject *PyExc_ModuleNotFoundError
-     * :exc:`ModuleNotFoundError`
-   * * .. c:var:: PyObject *PyExc_NameError
-     * :exc:`NameError`
-   * * .. c:var:: PyObject *PyExc_NotADirectoryError
-     * :exc:`NotADirectoryError`
-   * * .. c:var:: PyObject *PyExc_NotImplementedError
-     * :exc:`NotImplementedError`
-   * * .. c:var:: PyObject *PyExc_OSError
-     * :exc:`OSError`
-   * * .. c:var:: PyObject *PyExc_OverflowError
-     * :exc:`OverflowError`
-   * * .. c:var:: PyObject *PyExc_PermissionError
-     * :exc:`PermissionError`
-   * * .. c:var:: PyObject *PyExc_ProcessLookupError
-     * :exc:`ProcessLookupError`
-   * * .. c:var:: PyObject *PyExc_PythonFinalizationError
-     * :exc:`PythonFinalizationError`
-   * * .. c:var:: PyObject *PyExc_RecursionError
-     * :exc:`RecursionError`
-   * * .. c:var:: PyObject *PyExc_ReferenceError
-     * :exc:`ReferenceError`
-   * * .. c:var:: PyObject *PyExc_RuntimeError
-     * :exc:`RuntimeError`
-   * * .. c:var:: PyObject *PyExc_StopAsyncIteration
-     * :exc:`StopAsyncIteration`
-   * * .. c:var:: PyObject *PyExc_StopIteration
-     * :exc:`StopIteration`
-   * * .. c:var:: PyObject *PyExc_SyntaxError
-     * :exc:`SyntaxError`
-   * * .. c:var:: PyObject *PyExc_SystemError
-     * :exc:`SystemError`
-   * * .. c:var:: PyObject *PyExc_SystemExit
-     * :exc:`SystemExit`
-   * * .. c:var:: PyObject *PyExc_TabError
-     * :exc:`TabError`
-   * * .. c:var:: PyObject *PyExc_TimeoutError
-     * :exc:`TimeoutError`
-   * * .. c:var:: PyObject *PyExc_TypeError
-     * :exc:`TypeError`
-   * * .. c:var:: PyObject *PyExc_UnboundLocalError
-     * :exc:`UnboundLocalError`
-   * * .. c:var:: PyObject *PyExc_UnicodeDecodeError
-     * :exc:`UnicodeDecodeError`
-   * * .. c:var:: PyObject *PyExc_UnicodeEncodeError
-     * :exc:`UnicodeEncodeError`
-   * * .. c:var:: PyObject *PyExc_UnicodeError
-     * :exc:`UnicodeError`
-   * * .. c:var:: PyObject *PyExc_UnicodeTranslateError
-     * :exc:`UnicodeTranslateError`
-   * * .. c:var:: PyObject *PyExc_ValueError
-     * :exc:`ValueError`
-   * * .. c:var:: PyObject *PyExc_ZeroDivisionError
-     * :exc:`ZeroDivisionError`
+.. stable-abi-note::
+
+   These pointers are part of the :ref:`Stable ABI <stable>` since the
+   version in the *S.ABI* column (or since 3.2 when *S.ABI* is empty).
+   Exceptions marked N/A are *not* part of the Stable ABI.
+
+.. omit-stable-abi-notes::
+
+   .. list-table::
+      :align: left
+      :widths: auto
+      :header-rows: 1
+
+      * * C name
+        * .. stable-abi-note::
+
+             S.ABI
+        * Python name
+      * * .. c:var:: PyObject *PyExc_BaseException
+        *
+        * :exc:`BaseException`
+      * * .. c:var:: PyObject *PyExc_BaseExceptionGroup
+        * 3.11
+        * :exc:`BaseExceptionGroup`
+      * * .. c:var:: PyObject *PyExc_Exception
+        *
+        * :exc:`Exception`
+      * * .. c:var:: PyObject *PyExc_ArithmeticError
+        *
+        * :exc:`ArithmeticError`
+      * * .. c:var:: PyObject *PyExc_AssertionError
+        *
+        * :exc:`AssertionError`
+      * * .. c:var:: PyObject *PyExc_AttributeError
+        *
+        * :exc:`AttributeError`
+      * * .. c:var:: PyObject *PyExc_BlockingIOError
+        * 3.7
+        * :exc:`BlockingIOError`
+      * * .. c:var:: PyObject *PyExc_BrokenPipeError
+        * 3.7
+        * :exc:`BrokenPipeError`
+      * * .. c:var:: PyObject *PyExc_BufferError
+        *
+        * :exc:`BufferError`
+      * * .. c:var:: PyObject *PyExc_ChildProcessError
+        * 3.7
+        * :exc:`ChildProcessError`
+      * * .. c:var:: PyObject *PyExc_ConnectionAbortedError
+        * 3.7
+        * :exc:`ConnectionAbortedError`
+      * * .. c:var:: PyObject *PyExc_ConnectionError
+        * 3.7
+        * :exc:`ConnectionError`
+      * * .. c:var:: PyObject *PyExc_ConnectionRefusedError
+        * 3.7
+        * :exc:`ConnectionRefusedError`
+      * * .. c:var:: PyObject *PyExc_ConnectionResetError
+        * 3.7
+        * :exc:`ConnectionResetError`
+      * * .. c:var:: PyObject *PyExc_EOFError
+        *
+        * :exc:`EOFError`
+      * * .. c:var:: PyObject *PyExc_FileExistsError
+        * 3.7
+        * :exc:`FileExistsError`
+      * * .. c:var:: PyObject *PyExc_FileNotFoundError
+        * 3.7
+        * :exc:`FileNotFoundError`
+      * * .. c:var:: PyObject *PyExc_FloatingPointError
+        *
+        * :exc:`FloatingPointError`
+      * * .. c:var:: PyObject *PyExc_GeneratorExit
+        *
+        * :exc:`GeneratorExit`
+      * * .. c:var:: PyObject *PyExc_ImportCycleError
+        * N/A
+        * :exc:`ImportCycleError`
+      * * .. c:var:: PyObject *PyExc_ImportError
+        *
+        * :exc:`ImportError`
+      * * .. c:var:: PyObject *PyExc_IndentationError
+        *
+        * :exc:`IndentationError`
+      * * .. c:var:: PyObject *PyExc_IndexError
+        *
+        * :exc:`IndexError`
+      * * .. c:var:: PyObject *PyExc_InterruptedError
+        * 3.7
+        * :exc:`InterruptedError`
+      * * .. c:var:: PyObject *PyExc_IsADirectoryError
+        * 3.7
+        * :exc:`IsADirectoryError`
+      * * .. c:var:: PyObject *PyExc_KeyError
+        *
+        * :exc:`KeyError`
+      * * .. c:var:: PyObject *PyExc_KeyboardInterrupt
+        *
+        * :exc:`KeyboardInterrupt`
+      * * .. c:var:: PyObject *PyExc_LookupError
+        *
+        * :exc:`LookupError`
+      * * .. c:var:: PyObject *PyExc_MemoryError
+        *
+        * :exc:`MemoryError`
+      * * .. c:var:: PyObject *PyExc_ModuleNotFoundError
+        * 3.6
+        * :exc:`ModuleNotFoundError`
+      * * .. c:var:: PyObject *PyExc_NameError
+        *
+        * :exc:`NameError`
+      * * .. c:var:: PyObject *PyExc_NotADirectoryError
+        * 3.7
+        * :exc:`NotADirectoryError`
+      * * .. c:var:: PyObject *PyExc_NotImplementedError
+        *
+        * :exc:`NotImplementedError`
+      * * .. c:var:: PyObject *PyExc_OSError
+        *
+        * :exc:`OSError`
+      * * .. c:var:: PyObject *PyExc_OverflowError
+        *
+        * :exc:`OverflowError`
+      * * .. c:var:: PyObject *PyExc_PermissionError
+        * 3.7
+        * :exc:`PermissionError`
+      * * .. c:var:: PyObject *PyExc_ProcessLookupError
+        * 3.7
+        * :exc:`ProcessLookupError`
+      * * .. c:var:: PyObject *PyExc_PythonFinalizationError
+        *
+        * :exc:`PythonFinalizationError`
+      * * .. c:var:: PyObject *PyExc_RecursionError
+        * 3.7
+        * :exc:`RecursionError`
+      * * .. c:var:: PyObject *PyExc_ReferenceError
+        *
+        * :exc:`ReferenceError`
+      * * .. c:var:: PyObject *PyExc_RuntimeError
+        *
+        * :exc:`RuntimeError`
+      * * .. c:var:: PyObject *PyExc_StopAsyncIteration
+        * 3.7
+        * :exc:`StopAsyncIteration`
+      * * .. c:var:: PyObject *PyExc_StopIteration
+        *
+        * :exc:`StopIteration`
+      * * .. c:var:: PyObject *PyExc_SyntaxError
+        *
+        * :exc:`SyntaxError`
+      * * .. c:var:: PyObject *PyExc_SystemError
+        *
+        * :exc:`SystemError`
+      * * .. c:var:: PyObject *PyExc_SystemExit
+        *
+        * :exc:`SystemExit`
+      * * .. c:var:: PyObject *PyExc_TabError
+        *
+        * :exc:`TabError`
+      * * .. c:var:: PyObject *PyExc_TimeoutError
+        * 3.7
+        * :exc:`TimeoutError`
+      * * .. c:var:: PyObject *PyExc_TypeError
+        *
+        * :exc:`TypeError`
+      * * .. c:var:: PyObject *PyExc_UnboundLocalError
+        *
+        * :exc:`UnboundLocalError`
+      * * .. c:var:: PyObject *PyExc_UnicodeDecodeError
+        *
+        * :exc:`UnicodeDecodeError`
+      * * .. c:var:: PyObject *PyExc_UnicodeEncodeError
+        *
+        * :exc:`UnicodeEncodeError`
+      * * .. c:var:: PyObject *PyExc_UnicodeError
+        *
+        * :exc:`UnicodeError`
+      * * .. c:var:: PyObject *PyExc_UnicodeTranslateError
+        *
+        * :exc:`UnicodeTranslateError`
+      * * .. c:var:: PyObject *PyExc_ValueError
+        *
+        * :exc:`ValueError`
+      * * .. c:var:: PyObject *PyExc_ZeroDivisionError
+        *
+        * :exc:`ZeroDivisionError`
 
 .. versionadded:: 3.3
    :c:data:`PyExc_BlockingIOError`, :c:data:`PyExc_BrokenPipeError`,
@@ -1308,37 +1375,59 @@ Notes:
 Warning types
 -------------
 
-.. list-table::
-   :align: left
-   :widths: auto
-   :header-rows: 1
-
-   * * C name
-     * Python name
-   * * .. c:var:: PyObject *PyExc_Warning
-     * :exc:`Warning`
-   * * .. c:var:: PyObject *PyExc_BytesWarning
-     * :exc:`BytesWarning`
-   * * .. c:var:: PyObject *PyExc_DeprecationWarning
-     * :exc:`DeprecationWarning`
-   * * .. c:var:: PyObject *PyExc_EncodingWarning
-     * :exc:`EncodingWarning`
-   * * .. c:var:: PyObject *PyExc_FutureWarning
-     * :exc:`FutureWarning`
-   * * .. c:var:: PyObject *PyExc_ImportWarning
-     * :exc:`ImportWarning`
-   * * .. c:var:: PyObject *PyExc_PendingDeprecationWarning
-     * :exc:`PendingDeprecationWarning`
-   * * .. c:var:: PyObject *PyExc_ResourceWarning
-     * :exc:`ResourceWarning`
-   * * .. c:var:: PyObject *PyExc_RuntimeWarning
-     * :exc:`RuntimeWarning`
-   * * .. c:var:: PyObject *PyExc_SyntaxWarning
-     * :exc:`SyntaxWarning`
-   * * .. c:var:: PyObject *PyExc_UnicodeWarning
-     * :exc:`UnicodeWarning`
-   * * .. c:var:: PyObject *PyExc_UserWarning
-     * :exc:`UserWarning`
+.. stable-abi-note::
+
+   These pointers are part of the :ref:`Stable ABI <stable>` since the
+   version in the *S.ABI* column (or since 3.2 when *S.ABI* is empty).
+
+.. omit-stable-abi-notes::
+
+   .. list-table::
+      :align: left
+      :widths: auto
+      :header-rows: 1
+
+      * * C name
+        * .. stable-abi-note::
+
+           S.ABI
+        * Python name
+      * * .. c:var:: PyObject *PyExc_Warning
+        *
+        * :exc:`Warning`
+      * * .. c:var:: PyObject *PyExc_BytesWarning
+        *
+        * :exc:`BytesWarning`
+      * * .. c:var:: PyObject *PyExc_DeprecationWarning
+        *
+        * :exc:`DeprecationWarning`
+      * * .. c:var:: PyObject *PyExc_EncodingWarning
+        * 3.10
+        * :exc:`EncodingWarning`
+      * * .. c:var:: PyObject *PyExc_FutureWarning
+        *
+        * :exc:`FutureWarning`
+      * * .. c:var:: PyObject *PyExc_ImportWarning
+        *
+        * :exc:`ImportWarning`
+      * * .. c:var:: PyObject *PyExc_PendingDeprecationWarning
+        *
+        * :exc:`PendingDeprecationWarning`
+      * * .. c:var:: PyObject *PyExc_ResourceWarning
+        * 3.7
+        * :exc:`ResourceWarning`
+      * * .. c:var:: PyObject *PyExc_RuntimeWarning
+        *
+        * :exc:`RuntimeWarning`
+      * * .. c:var:: PyObject *PyExc_SyntaxWarning
+        *
+        * :exc:`SyntaxWarning`
+      * * .. c:var:: PyObject *PyExc_UnicodeWarning
+        *
+        * :exc:`UnicodeWarning`
+      * * .. c:var:: PyObject *PyExc_UserWarning
+        *
+        * :exc:`UserWarning`
 
 .. versionadded:: 3.2
    :c:data:`PyExc_ResourceWarning`.
diff --git a/Doc/c-api/iterator.rst b/Doc/c-api/iterator.rst
index bfbfe3c92799809..2276620cc303141 100644
--- a/Doc/c-api/iterator.rst
+++ b/Doc/c-api/iterator.rst
@@ -72,23 +72,28 @@ Builtin Iterator Types
 These are built-in iteration types that are included in Python's C API, but
 provide no additional functions. They are here for completeness.
 
-
-.. list-table::
-   :widths: auto
-   :header-rows: 1
-
-   * * C type
-     * Python type
-   * * .. c:var:: PyTypeObject PyEnum_Type
-     * :py:class:`enumerate`
-   * * .. c:var:: PyTypeObject PyFilter_Type
-     * :py:class:`filter`
-   * * .. c:var:: PyTypeObject PyMap_Type
-     * :py:class:`map`
-   * * .. c:var:: PyTypeObject PyReversed_Type
-     * :py:class:`reversed`
-   * * .. c:var:: PyTypeObject PyZip_Type
-     * :py:class:`zip`
+.. stable-abi-note::
+
+   These pointers are part of the :ref:`Stable ABI <stable>`.
+
+.. omit-stable-abi-notes::
+
+   .. list-table::
+      :widths: auto
+      :header-rows: 1
+
+      * * C type
+        * Python type
+      * * .. c:var:: PyTypeObject PyEnum_Type
+        * :py:class:`enumerate`
+      * * .. c:var:: PyTypeObject PyFilter_Type
+        * :py:class:`filter`
+      * * .. c:var:: PyTypeObject PyMap_Type
+        * :py:class:`map`
+      * * .. c:var:: PyTypeObject PyReversed_Type
+        * :py:class:`reversed`
+      * * .. c:var:: PyTypeObject PyZip_Type
+        * :py:class:`zip`
 
 
 Other Iterator Objects
diff --git a/Doc/tools/extensions/c_annotations.py 
b/Doc/tools/extensions/c_annotations.py
index fbbf01fa7da02bd..7a7badb31c96014 100644
--- a/Doc/tools/extensions/c_annotations.py
+++ b/Doc/tools/extensions/c_annotations.py
@@ -197,8 +197,21 @@ def add_annotations(app: Sphinx, doctree: nodes.document) 
-> None:
                     f"{objtype!r} not in {record_object_types!r}"
                 )
                 raise ValueError(msg)
-            annotation = _stable_abi_annotation(record)
-            node.insert(0, annotation)
+
+            # Skip the note if any ancestor has 'omit-stable-abi-notes'
+            # in a 'c_annotations' attribute.
+            ancestor = node
+            while ancestor:
+                if 'omit-stable-abi-notes' in ancestor.get(
+                    'c_annotations',
+                    [],
+                ):
+                    break
+                ancestor = ancestor.parent
+            else:
+                # no skip; add the annotation
+                annotation = _stable_abi_annotation(record)
+                node.insert(0, annotation)
 
         # Unstable API annotation.
         if name.startswith("PyUnstable"):
@@ -463,6 +476,43 @@ def run(self) -> list[nodes.Node]:
         return [node]
 
 
+class StableABINote(SphinxDirective):
+    """A manual Stable ABI note
+
+    Normally, notes are auto-generated.
+    This directive allows adding a similarly styled note manually.
+    Pair with omit-stable-abi-notes to override the automatic generation.
+    """
+
+    has_content = True
+
+    def run(self) -> list[nodes.Node]:
+        self.assert_has_content()
+        nodes = self.parse_content_to_nodes()
+        for node in nodes:
+            node.setdefault("classes", []).append('stableabi')
+        return nodes
+
+
+class OmitStableABINotes(SphinxDirective):
+    """A block where automatic Stable ABI notes are not generated
+
+    Normally used for dense lists/tables of definitions, where individual notes
+    are omitted and the whole block has a common manual note.
+    """
+
+    has_content = True
+
+    def run(self) -> list[nodes.Node]:
+        self.assert_has_content()
+        nodes = self.parse_content_to_nodes()
+        for node in nodes:
+            node.setdefault("c_annotations", []).append(
+                'omit-stable-abi-notes',
+            )
+        return nodes
+
+
 def init_annotations(app: Sphinx) -> None:
     # Using domaindata is a bit hack-ish,
     # but allows storing state without a global variable or closure.
@@ -485,6 +535,8 @@ def setup(app: Sphinx) -> ExtensionMetadata:
     app.add_directive("limited-api-list", LimitedAPIList)
     app.add_directive("version-hex-cheatsheet", VersionHexCheatsheet)
     app.add_directive("corresponding-type-slot", CorrespondingTypeSlot)
+    app.add_directive("omit-stable-abi-notes", OmitStableABINotes)
+    app.add_directive("stable-abi-note", StableABINote)
     app.connect("builder-inited", init_annotations)
     app.connect("doctree-read", add_annotations)
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to