From 37c2d222f609c43189f45813f3c4eb9a00b555e0 Mon Sep 17 00:00:00 2001 From: Owen Carey <37121709+owenthcarey@users.noreply.github.com> Date: Thu, 3 Sep 2026 00:30:20 -0700 Subject: [PATCH] gh-156870: Use a manual Stable ABI note for the exception type tables In compact tables of many similar definitions, the generated "Part of the Stable ABI" notes are repetitive and take up a lot of vertical space. Let the c_annotations extension skip the generated note for elements inside a container with the "no-stable-abi-notes" class, and use it for the three tables of exception and warning types in the C API docs. Each table gets an "S.ABI" column with the version in which the pointer was added to the Stable ABI, and a common note before it. --- Doc/c-api/exceptions.rst | 87 +++++++++++++++++++++++++++ Doc/tools/extensions/c_annotations.py | 30 ++++++++- 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 40522f8c7b13756..a82234a2d3f768d 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -1117,124 +1117,186 @@ For completeness, here are all the variables: Exception types --------------- +These pointers are part of the :ref:`Stable ABI ` since the version +in the *S.ABI* column, or since 3.2 if that column is empty. +Exceptions marked N/A are *not* part of the Stable ABI. + .. list-table:: :align: left :widths: auto :header-rows: 1 + :class: no-stable-abi-notes * * C name + * :ref:`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 + * N/A * :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 @@ -1265,21 +1327,29 @@ The following are a compatibility aliases to :c:data:`PyExc_OSError`. .. versionchanged:: 3.3 These aliases used to be separate exception types. +These pointers are part of the :ref:`Stable ABI ` since the version +in the *S.ABI* column, or since 3.2 if that column is empty. + .. list-table:: :align: left :widths: auto :header-rows: 1 + :class: no-stable-abi-notes * * C name + * :ref:`S.ABI ` * Python name * Notes * * .. c:var:: PyObject *PyExc_EnvironmentError + * * :exc:`OSError` * * * .. c:var:: PyObject *PyExc_IOError + * * :exc:`OSError` * * * .. c:var:: PyObject *PyExc_WindowsError + * 3.7 * :exc:`OSError` * [win]_ @@ -1295,36 +1365,53 @@ Notes: Warning types ------------- +These pointers are part of the :ref:`Stable ABI ` since the version +in the *S.ABI* column, or since 3.2 if that column is empty. + .. list-table:: :align: left :widths: auto :header-rows: 1 + :class: no-stable-abi-notes * * C name + * :ref:`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 diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py index 0e762042979c2b7..c21dba78eaa006a 100644 --- a/Doc/tools/extensions/c_annotations.py +++ b/Doc/tools/extensions/c_annotations.py @@ -36,6 +36,11 @@ "data": "var", } +# Class that suppresses the generated Stable ABI notes for all C API +# elements documented inside the element it is set on (see +# ``_stable_abi_note_suppressed``). +NO_STABLE_ABI_NOTES_CLASS = "no-stable-abi-notes" + @dataclasses.dataclass(slots=True) class RefCountEntry: @@ -183,8 +188,9 @@ def add_annotations(app: Sphinx, doctree: nodes.document) -> None: f"{ROLE_TO_OBJECT_TYPE[record.role]!r} != {objtype!r}" ) raise ValueError(msg) - annotation = _stable_abi_annotation(record) - node.insert(0, annotation) + if not _stable_abi_note_suppressed(node): + annotation = _stable_abi_annotation(record) + node.insert(0, annotation) # Unstable API annotation. if name.startswith("PyUnstable"): @@ -203,6 +209,26 @@ def add_annotations(app: Sphinx, doctree: nodes.document) -> None: node.insert(0, annotation) +def _stable_abi_note_suppressed(node: nodes.Element) -> bool: + """Return whether an ancestor of *node* opts out of Stable ABI notes. + + In compact tables of many similar definitions, the generated notes + get repetitive and take up too much vertical space. + Such a table can instead carry a single manually written note, and + suppress the generated ones by setting the ``no-stable-abi-notes`` + class, for example:: + + .. list-table:: + :class: no-stable-abi-notes + """ + ancestor = node.parent + while ancestor is not None: + if NO_STABLE_ABI_NOTES_CLASS in ancestor.get("classes", ()): + return True + ancestor = ancestor.parent + return False + + def _stable_abi_annotation( record: StableABIEntry, is_corresponding_slot: bool = False,