--- Doc/Makefile | 8 +-- Doc/c-api/arg.rst | 1 Doc/c-api/typeobj.rst | 8 +-- Doc/conf.py | 29 ++++++++++--- Doc/howto/free-threading-python.rst | 2 Doc/library/doctest.rst | 1 Doc/library/email.compat32-message.rst | 1 Doc/library/xml.etree.elementtree.rst | 1 Doc/c-api/arg.rst | 1 Doc/c-api/typeobj.rst | 8 +-- Doc/conf.py | 29 ++++++++++--- Doc/library/doctest.rst | 1 Doc/library/email.compat32-message.rst | 1 Doc/library/xml.etree.elementtree.rst | 1 Doc/tools/check-warnings.py | 5 +- Doc/tools/extensions/audit_events.py | 56 ++++++++++++++------------ Doc/tools/extensions/availability.py | 15 +++--- Doc/tools/extensions/c_annotations.py | 53 ++++++++++++++++-------- Doc/tools/extensions/changes.py | 8 +-- Doc/tools/extensions/glossary_search.py | 20 ++++++--- Doc/tools/extensions/implementation_detail.py | 22 +++------- Doc/tools/extensions/issue_role.py | 16 ++----- Doc/tools/extensions/misc_news.py | 14 ++---- Doc/tools/extensions/patchlevel.py | 9 ++-- Doc/tools/extensions/pydoc_topics.py | 22 +++++----- 17 files changed, 155 insertions(+), 126 deletions(-) Index: Python-3.13.9/Doc/c-api/arg.rst =================================================================== --- Python-3.13.9.orig/Doc/c-api/arg.rst 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/c-api/arg.rst 2025-11-04 17:41:42.876411055 +0100 @@ -334,7 +334,6 @@ should raise an exception and leave the content of *address* unmodified. .. c:macro:: Py_CLEANUP_SUPPORTED - :no-typesetting: If the *converter* returns :c:macro:`!Py_CLEANUP_SUPPORTED`, it may get called a second time if the argument parsing eventually fails, giving the converter a Index: Python-3.13.9/Doc/c-api/typeobj.rst =================================================================== --- Python-3.13.9.orig/Doc/c-api/typeobj.rst 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/c-api/typeobj.rst 2025-11-04 17:41:42.877033887 +0100 @@ -610,7 +610,7 @@ Functions like :c:func:`PyObject_NewVar` will take the value of N as an argument, and store in the instance's :c:member:`~PyVarObject.ob_size` field. Note that the :c:member:`~PyVarObject.ob_size` field may later be used for - other purposes. For example, :py:type:`int` instances use the bits of + other purposes. For example, :py:obj:`int` instances use the bits of :c:member:`~PyVarObject.ob_size` in an implementation-defined way; the underlying storage and its size should be accessed using :c:func:`PyLong_Export`. @@ -622,9 +622,9 @@ Also, the presence of an :c:member:`~PyVarObject.ob_size` field in the instance layout doesn't mean that the instance structure is variable-length. - For example, the :py:type:`list` type has fixed-length instances, yet those + For example, the :py:obj:`list` type has fixed-length instances, yet those instances have a :c:member:`~PyVarObject.ob_size` field. - (As with :py:type:`int`, avoid reading lists' :c:member:`!ob_size` directly. + (As with :py:obj:`int`, avoid reading lists' :c:member:`!ob_size` directly. Call :c:func:`PyList_Size` instead.) The :c:member:`!tp_basicsize` includes size needed for data of the type's @@ -637,7 +637,7 @@ In other words, :c:member:`!tp_basicsize` must be greater than or equal to the base's :c:member:`!tp_basicsize`. - Since every type is a subtype of :py:type:`object`, this struct must + Since every type is a subtype of :py:obj:`object`, this struct must include :c:type:`PyObject` or :c:type:`PyVarObject` (depending on whether :c:member:`~PyVarObject.ob_size` should be included). These are usually defined by the macro :c:macro:`PyObject_HEAD` or Index: Python-3.13.9/Doc/conf.py =================================================================== --- Python-3.13.9.orig/Doc/conf.py 2025-11-04 17:39:03.414159687 +0100 +++ Python-3.13.9/Doc/conf.py 2025-11-04 17:41:42.877735198 +0100 @@ -11,6 +11,8 @@ from importlib import import_module from importlib.util import find_spec +from sphinx import version_info + # Make our custom extensions available to Sphinx sys.path.append(os.path.abspath('tools/extensions')) sys.path.append(os.path.abspath('includes')) @@ -57,11 +59,11 @@ import _tkinter except ImportError: _tkinter = None -# Treat warnings as errors, done here to prevent warnings in Sphinx code from -# causing spurious CPython test failures. -import warnings -warnings.simplefilter('error') -del warnings +# # Treat warnings as errors, done here to prevent warnings in Sphinx code from +# # causing spurious CPython test failures. +# import warnings +# warnings.simplefilter('error') +# del warnings ''' manpages_url = 'https://manpages.debian.org/{path}' @@ -96,7 +98,7 @@ # Minimum version of sphinx required # Keep this version in sync with ``Doc/requirements.txt``. -needs_sphinx = '8.2.0' +needs_sphinx = '4.2.0' # Create table of contents entries for domain objects (e.g. functions, classes, # attributes, etc.). Default is True. @@ -257,6 +259,9 @@ # Avoid a warning with Sphinx >= 4.0 root_doc = 'contents' +# Compatibility on old Sphinx +suppress_warnings = ['pygments.ParserNotFound'] + # Allow translation of index directives gettext_additional_targets = [ 'index', @@ -296,7 +301,7 @@ # (See .readthedocs.yml and https://docs.readthedocs.io/en/stable/reference/environment-variables.html) is_deployment_preview = os.getenv("READTHEDOCS_VERSION_TYPE") == "external" repository_url = os.getenv("READTHEDOCS_GIT_CLONE_URL", "") -repository_url = repository_url.removesuffix(".git") +repository_url = repository_url[:-len(".git")] html_context = { "is_deployment_preview": is_deployment_preview, "repository_url": repository_url or None, @@ -542,6 +547,16 @@ } extlinks_detect_hardcoded_links = True +if version_info[:2] < (8, 1): + # Sphinx 8.1 has in-built CVE and CWE roles. + extlinks.update({ + "cve": ( + "https://www.cve.org/CVERecord?id=CVE-%s", + "CVE-%s", + ), + "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"), + }) + # Options for c_annotations extension # ----------------------------------- Index: Python-3.13.9/Doc/library/doctest.rst =================================================================== --- Python-3.13.9.orig/Doc/library/doctest.rst 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/library/doctest.rst 2025-11-04 17:41:42.878188221 +0100 @@ -310,7 +310,6 @@ .. currentmodule:: None .. attribute:: module.__test__ - :no-typesetting: .. currentmodule:: doctest Index: Python-3.13.9/Doc/library/email.compat32-message.rst =================================================================== --- Python-3.13.9.orig/Doc/library/email.compat32-message.rst 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/library/email.compat32-message.rst 2025-11-04 17:41:42.878726754 +0100 @@ -7,7 +7,6 @@ :synopsis: The base class representing email messages in a fashion backward compatible with Python 3.2 :noindex: - :no-index: The :class:`Message` class is very similar to the Index: Python-3.13.9/Doc/library/xml.etree.elementtree.rst =================================================================== --- Python-3.13.9.orig/Doc/library/xml.etree.elementtree.rst 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/library/xml.etree.elementtree.rst 2025-11-04 17:41:42.879107050 +0100 @@ -873,7 +873,6 @@ .. module:: xml.etree.ElementTree :noindex: - :no-index: .. class:: Element(tag, attrib={}, **extra) Index: Python-3.13.9/Doc/tools/check-warnings.py =================================================================== --- Python-3.13.9.orig/Doc/tools/check-warnings.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/check-warnings.py 2025-11-04 17:41:42.879425179 +0100 @@ -228,7 +228,8 @@ print(filename) for warning in warnings: if filename in warning: - if match := WARNING_PATTERN.fullmatch(warning): + match = WARNING_PATTERN.fullmatch(warning) + if match: print(" {line}: {msg}".format_map(match)) return -1 return 0 @@ -316,7 +317,7 @@ cwd = str(Path.cwd()) + os.path.sep files_with_nits = { - warning.removeprefix(cwd).split(":")[0] + (warning[len(cwd):].split(":")[0] if warning.startswith(cwd) else warning.split(":")[0]) for warning in warnings if "Doc/" in warning } Index: Python-3.13.9/Doc/tools/extensions/audit_events.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/audit_events.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/audit_events.py 2025-11-04 17:41:42.879679368 +0100 @@ -1,9 +1,6 @@ """Support for documenting audit events.""" -from __future__ import annotations - import re -from typing import TYPE_CHECKING from docutils import nodes from sphinx.errors import NoUri @@ -12,12 +9,19 @@ from sphinx.util import logging from sphinx.util.docutils import SphinxDirective -if TYPE_CHECKING: - from collections.abc import Iterator, Set +from typing import Any, Iterator, List, Set, Tuple + +from sphinx.application import Sphinx +from sphinx.builders import Builder +from sphinx.environment import BuildEnvironment + +# --- The Monkey Patch --- +def findall_patch(self, *args, **kwargs): + """A backwards-compatible findall method that calls traverse.""" + return self.traverse(*args, **kwargs) - from sphinx.application import Sphinx - from sphinx.builders import Builder - from sphinx.environment import BuildEnvironment +if not hasattr(nodes.Node, 'findall'): + nodes.Node.findall = findall_patch logger = logging.getLogger(__name__) @@ -32,16 +36,16 @@ class AuditEvents: def __init__(self) -> None: - self.events: dict[str, list[str]] = {} - self.sources: dict[str, set[tuple[str, str]]] = {} + self.events: dict[str, List[str]] = {} + self.sources: dict[str, Set[Tuple[str, str]]] = {} - def __iter__(self) -> Iterator[tuple[str, list[str], tuple[str, str]]]: + def __iter__(self) -> Iterator[Any]: for name, args in self.events.items(): for source in self.sources[name]: yield name, args, source def add_event( - self, name, args: list[str], source: tuple[str, str] + self, name, args: List[str], source: Tuple[str, str] ) -> None: if name in self.events: self._check_args_match(name, args) @@ -49,7 +53,7 @@ self.events[name] = args self.sources.setdefault(name, set()).add(source) - def _check_args_match(self, name: str, args: list[str]) -> None: + def _check_args_match(self, name: str, args: List[str]) -> None: current_args = self.events[name] msg = ( f"Mismatched arguments for audit-event {name}: " @@ -60,7 +64,7 @@ if len(current_args) != len(args): logger.warning(msg) return - for a1, a2 in zip(current_args, args, strict=False): + for a1, a2 in zip(current_args, args): if a1 == a2: continue if any(a1 in s and a2 in s for s in _SYNONYMS): @@ -73,7 +77,7 @@ name_clean = re.sub(r"\W", "_", name) return f"audit_event_{name_clean}_{source_count}" - def rows(self) -> Iterator[tuple[str, list[str], Set[tuple[str, str]]]]: + def rows(self) -> Iterator[Any]: for name in sorted(self.events.keys()): yield name, self.events[name], self.sources[name] @@ -97,7 +101,7 @@ def audit_events_merge( app: Sphinx, env: BuildEnvironment, - docnames: list[str], + docnames: List[str], other: BuildEnvironment, ) -> None: """In Sphinx parallel builds, this merges audit_events from subprocesses.""" @@ -126,14 +130,16 @@ ), ] - def run(self) -> list[nodes.paragraph]: + def run(self) -> List[nodes.paragraph]: + def _no_walrus_op(args): + for arg in args.strip("'\"").split(","): + aarg = arg.strip() + if aarg: + yield aarg + name = self.arguments[0] if len(self.arguments) >= 2 and self.arguments[1]: - args = [ - arg - for argument in self.arguments[1].strip("'\"").split(",") - if (arg := argument.strip()) - ] + args = list(_no_walrus_op(self.arguments[1])) else: args = [] ids = [] @@ -169,7 +175,7 @@ class AuditEventListDirective(SphinxDirective): - def run(self) -> list[audit_event_list]: + def run(self) -> List[audit_event_list]: return [audit_event_list()] @@ -217,8 +223,8 @@ builder: Builder, docname: str, name: str, - args: list[str], - sources: Set[tuple[str, str]], + args: List[str], + sources: Set[Tuple[str, str]], ) -> nodes.row: row = nodes.row() name_node = nodes.paragraph("", nodes.Text(name)) Index: Python-3.13.9/Doc/tools/extensions/availability.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/availability.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/availability.py 2025-11-04 17:41:42.879900324 +0100 @@ -1,8 +1,6 @@ """Support for documenting platform availability""" -from __future__ import annotations - -from typing import TYPE_CHECKING +from typing import Dict, List, TYPE_CHECKING, Union from docutils import nodes from sphinx import addnodes @@ -55,7 +53,7 @@ optional_arguments = 0 final_argument_whitespace = True - def run(self) -> list[nodes.container]: + def run(self) -> List[nodes.container]: title = sphinx_gettext("Availability") refnode = addnodes.pending_xref( title, @@ -79,7 +77,7 @@ return [cnode] - def parse_platforms(self) -> dict[str, str | bool]: + def parse_platforms(self) -> Dict[str, Union[str, bool]]: """Parse platform information from arguments Arguments is a comma-separated string of platforms. A platform may @@ -98,12 +96,13 @@ platform, _, version = arg.partition(" >= ") if platform.startswith("not "): version = False - platform = platform.removeprefix("not ") + platform = platform[len("not "):] elif not version: version = True platforms[platform] = version - if unknown := set(platforms).difference(KNOWN_PLATFORMS): + unknown = set(platforms).difference(KNOWN_PLATFORMS) + if unknown: logger.warning( "Unknown platform%s or syntax '%s' in '.. availability:: %s', " "see %s:KNOWN_PLATFORMS for a set of known platforms.", @@ -116,7 +115,7 @@ return platforms -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app): app.add_directive("availability", Availability) return { Index: Python-3.13.9/Doc/tools/extensions/c_annotations.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/c_annotations.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/c_annotations.py 2025-11-04 17:41:42.880074051 +0100 @@ -9,22 +9,26 @@ * Set ``stable_abi_file`` to the path to stable ABI list. """ -from __future__ import annotations - import csv import dataclasses from pathlib import Path -from typing import TYPE_CHECKING +from typing import Any, Dict, List, TYPE_CHECKING, Union from docutils import nodes from docutils.statemachine import StringList -from sphinx import addnodes +from sphinx import addnodes, version_info from sphinx.locale import _ as sphinx_gettext from sphinx.util.docutils import SphinxDirective -if TYPE_CHECKING: - from sphinx.application import Sphinx - from sphinx.util.typing import ExtensionMetadata +from sphinx.application import Sphinx + +# --- The Monkey Patch --- +def findall_patch(self, *args, **kwargs): + """A backwards-compatible findall method that calls traverse.""" + return self.traverse(*args, **kwargs) + +if not hasattr(nodes.Node, 'findall'): + nodes.Node.findall = findall_patch ROLE_TO_OBJECT_TYPE = { "func": "function", @@ -35,20 +39,20 @@ } -@dataclasses.dataclass(slots=True) +@dataclasses.dataclass() class RefCountEntry: # Name of the function. name: str # List of (argument name, type, refcount effect) tuples. # (Currently not used. If it was, a dataclass might work better.) - args: list = dataclasses.field(default_factory=list) + args: List = dataclasses.field(default_factory=list) # Return type of the function. result_type: str = "" # Reference count effect for the return value. - result_refs: int | None = None + result_refs: Union[int, None] = None -@dataclasses.dataclass(frozen=True, slots=True) +@dataclasses.dataclass(frozen=True) class StableABIEntry: # Role of the object. # Source: Each [item_kind] in stable_abi.toml is mapped to a C Domain role. @@ -67,7 +71,7 @@ struct_abi_kind: str -def read_refcount_data(refcount_filename: Path) -> dict[str, RefCountEntry]: +def read_refcount_data(refcount_filename: Path) -> Dict[str, RefCountEntry]: refcount_data = {} refcounts = refcount_filename.read_text(encoding="utf8") for line in refcounts.splitlines(): @@ -103,7 +107,7 @@ return refcount_data -def read_stable_abi_data(stable_abi_file: Path) -> dict[str, StableABIEntry]: +def read_stable_abi_data(stable_abi_file: Path) -> Dict[str, StableABIEntry]: stable_abi_data = {} with open(stable_abi_file, encoding="utf8") as fp: for record in csv.DictReader(fp): @@ -123,11 +127,14 @@ continue if not par[0].get("ids", None): continue - name = par[0]["ids"][0].removeprefix("c.") + name = par[0]["ids"][0] + if name.startswith("c."): + name = name[len("c."):] objtype = par["objtype"] # Stable ABI annotation. - if record := stable_abi_data.get(name): + record = stable_abi_data.get(name) + if record: if ROLE_TO_OBJECT_TYPE[record.role] != objtype: msg = ( f"Object type mismatch in limited API annotation for {name}: " @@ -234,7 +241,7 @@ ) -def _return_value_annotation(result_refs: int | None) -> nodes.emphasis: +def _return_value_annotation(result_refs: Union[int, None]) -> nodes.emphasis: classes = ["refcount"] if result_refs is None: rc = sphinx_gettext("Return value: Always NULL.") @@ -254,7 +261,7 @@ optional_arguments = 0 final_argument_whitespace = True - def run(self) -> list[nodes.Node]: + def run(self) -> List[nodes.Node]: state = self.env.domaindata["c_annotations"] content = [ f"* :c:{record.role}:`{record.name}`" @@ -277,13 +284,23 @@ ) -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app: Sphinx) -> Any: app.add_config_value("refcount_file", "", "env", types={str}) app.add_config_value("stable_abi_file", "", "env", types={str}) app.add_directive("limited-api-list", LimitedAPIList) app.connect("builder-inited", init_annotations) app.connect("doctree-read", add_annotations) + if version_info[:2] < (7, 2): + from docutils.parsers.rst import directives + from sphinx.domains.c import CObject + + # monkey-patch C object... + CObject.option_spec.update({ + "no-index-entry": directives.flag, + "no-contents-entry": directives.flag, + }) + return { "version": "1.0", "parallel_read_safe": True, Index: Python-3.13.9/Doc/tools/extensions/changes.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/changes.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/changes.py 2025-11-04 17:41:42.880259370 +0100 @@ -1,7 +1,5 @@ """Support for documenting version of changes, additions, deprecations.""" -from __future__ import annotations - from typing import TYPE_CHECKING from sphinx.domains.changeset import ( @@ -25,7 +23,7 @@ class PyVersionChange(VersionChange): - def run(self) -> list[Node]: + def run(self) -> "list[Node]": # Replace the 'next' special token with the current development version self.arguments[0] = expand_version_arg( self.arguments[0], self.config.release @@ -43,7 +41,7 @@ "Deprecated since version %s, removed in version %s" ) - def run(self) -> list[Node]: + def run(self) -> "list[Node]": # Replace the first two arguments (deprecated version and removed version) # with a single tuple of both versions. version_deprecated = expand_version_arg( @@ -73,7 +71,7 @@ versionlabel_classes[self.name] = "" -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app: "Sphinx") -> "ExtensionMetadata": # Override Sphinx's directives with support for 'next' app.add_directive("versionadded", PyVersionChange, override=True) app.add_directive("versionchanged", PyVersionChange, override=True) Index: Python-3.13.9/Doc/tools/extensions/glossary_search.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/glossary_search.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/glossary_search.py 2025-11-04 17:41:42.880446332 +0100 @@ -1,21 +1,27 @@ """Feature search results for glossary items prominently.""" -from __future__ import annotations - import json from pathlib import Path -from typing import TYPE_CHECKING +from typing import Any, TYPE_CHECKING from docutils import nodes from sphinx.addnodes import glossary from sphinx.util import logging -if TYPE_CHECKING: - from sphinx.application import Sphinx - from sphinx.util.typing import ExtensionMetadata +from sphinx.application import Sphinx logger = logging.getLogger(__name__) +from docutils import nodes +from sphinx import addnodes + +# --- The Monkey Patch --- +def findall_patch(self, *args, **kwargs): + """A backwards-compatible findall method that calls traverse.""" + return self.traverse(*args, **kwargs) + +if not hasattr(nodes.Node, 'findall'): + nodes.Node.findall = findall_patch def process_glossary_nodes( app: Sphinx, @@ -52,7 +58,7 @@ dest.write_text(json.dumps(app.env.glossary_terms), encoding='utf-8') -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app: Sphinx) -> Any: app.connect('doctree-resolved', process_glossary_nodes) app.connect('build-finished', write_glossary_json) Index: Python-3.13.9/Doc/tools/extensions/implementation_detail.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/implementation_detail.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/implementation_detail.py 2025-11-04 17:41:42.880613957 +0100 @@ -1,17 +1,10 @@ """Support for marking up implementation details.""" -from __future__ import annotations - -from typing import TYPE_CHECKING - from docutils import nodes from sphinx.locale import _ as sphinx_gettext from sphinx.util.docutils import SphinxDirective -if TYPE_CHECKING: - from sphinx.application import Sphinx - from sphinx.util.typing import ExtensionMetadata - +from sphinx.application import Sphinx class ImplementationDetail(SphinxDirective): has_content = True @@ -21,23 +14,24 @@ label_text = sphinx_gettext("CPython implementation detail:") def run(self): - self.assert_has_content() - content_nodes = self.parse_content_to_nodes() + container_node = nodes.container() + container_node.document = self.state.document # Ensure node has document context + self.state.nested_parse(self.content, self.content_offset, container_node) + parsed_nodes = container_node.children # insert our prefix at the start of the first paragraph - first_node = content_nodes[0] + first_node = parsed_nodes[0] first_node[:0] = [ nodes.strong(self.label_text, self.label_text), nodes.Text(" "), ] - # create a new compound container node - cnode = nodes.compound("", *content_nodes, classes=["impl-detail"]) + cnode = nodes.compound("", *parsed_nodes, classes=["impl-detail"]) self.set_source_info(cnode) return [cnode] -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app: Sphinx): app.add_directive("impl-detail", ImplementationDetail) return { Index: Python-3.13.9/Doc/tools/extensions/issue_role.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/issue_role.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/issue_role.py 2025-11-04 17:41:42.880769320 +0100 @@ -1,22 +1,18 @@ """Support for referencing issues in the tracker.""" -from __future__ import annotations - -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Tuple from docutils import nodes from sphinx.util.docutils import SphinxRole -if TYPE_CHECKING: - from docutils.nodes import Element - from sphinx.application import Sphinx - from sphinx.util.typing import ExtensionMetadata +from docutils.nodes import Element +from sphinx.application import Sphinx class BPOIssue(SphinxRole): ISSUE_URI = "https://bugs.python.org/issue?@action=redirect&bpo={0}" - def run(self) -> tuple[list[Element], list[nodes.system_message]]: + def run(self) -> Tuple[List[Element], List[nodes.system_message]]: issue = self.text # sanity check: there are no bpo issues within these two values @@ -38,7 +34,7 @@ class GitHubIssue(SphinxRole): ISSUE_URI = "https://github.com/python/cpython/issues/{0}" - def run(self) -> tuple[list[Element], list[nodes.system_message]]: + def run(self) -> Tuple[List[Element], List[nodes.system_message]]: issue = self.text # sanity check: all GitHub issues have ID >= 32426 @@ -58,7 +54,7 @@ return [refnode], [] -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app: Sphinx) -> "ExtensionMetadata": app.add_role("issue", BPOIssue()) app.add_role("gh", GitHubIssue()) Index: Python-3.13.9/Doc/tools/extensions/misc_news.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/misc_news.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/misc_news.py 2025-11-04 17:41:42.880942406 +0100 @@ -1,7 +1,5 @@ """Support for including Misc/NEWS.""" -from __future__ import annotations - import re from pathlib import Path from typing import TYPE_CHECKING @@ -24,13 +22,13 @@ +++++++++++ """ -bpo_issue_re: Final[re.Pattern[str]] = re.compile( +bpo_issue_re: "Final[re.Pattern[str]]" = re.compile( "(?:issue #|bpo-)([0-9]+)", re.ASCII ) -gh_issue_re: Final[re.Pattern[str]] = re.compile( +gh_issue_re: "Final[re.Pattern[str]]" = re.compile( "gh-(?:issue-)?([0-9]+)", re.ASCII | re.IGNORECASE ) -whatsnew_re: Final[re.Pattern[str]] = re.compile( +whatsnew_re: "Final[re.Pattern[str]]" = re.compile( r"^what's new in (.*?)\??$", re.ASCII | re.IGNORECASE | re.MULTILINE ) @@ -42,7 +40,7 @@ final_argument_whitespace = False option_spec = {} - def run(self) -> list[Node]: + def run(self) -> "list[Node]": # Get content of NEWS file source, _ = self.get_source_info() news_file = Path(source).resolve().parent / self.arguments[0] @@ -54,7 +52,7 @@ return [nodes.strong(text, text)] # remove first 3 lines as they are the main heading - news_text = news_text.removeprefix(BLURB_HEADER) + news_text = news_text[len(BLURB_HEADER):] if news_text.startswith(BLURB_HEADER) else news_text news_text = bpo_issue_re.sub(r":issue:`\1`", news_text) # Fallback handling for GitHub issues @@ -65,7 +63,7 @@ return [] -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app: "Sphinx") -> "ExtensionMetadata": app.add_directive("miscnews", MiscNews) return { Index: Python-3.13.9/Doc/tools/extensions/patchlevel.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/patchlevel.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/patchlevel.py 2025-11-04 17:41:42.881098319 +0100 @@ -3,7 +3,7 @@ import re import sys from pathlib import Path -from typing import Literal, NamedTuple +from typing import NamedTuple, Tuple CPYTHON_ROOT = Path( __file__, # cpython/Doc/tools/extensions/patchlevel.py @@ -26,7 +26,7 @@ major: int #: Major release number minor: int #: Minor release number micro: int #: Patch release number - releaselevel: Literal["alpha", "beta", "candidate", "final"] + releaselevel: str serial: int #: Serial release number @@ -37,7 +37,8 @@ defines = {} patchlevel_h = PATCHLEVEL_H.read_text(encoding="utf-8") for line in patchlevel_h.splitlines(): - if (m := pat.match(line)) is not None: + m = pat.match(line) + if m is not None: name, value = m.groups() defines[name] = value @@ -50,7 +51,7 @@ ) -def format_version_info(info: version_info) -> tuple[str, str]: +def format_version_info(info: version_info) -> Tuple[str, str]: version = f"{info.major}.{info.minor}" release = f"{info.major}.{info.minor}.{info.micro}" if info.releaselevel != "final": Index: Python-3.13.9/Doc/tools/extensions/pydoc_topics.py =================================================================== --- Python-3.13.9.orig/Doc/tools/extensions/pydoc_topics.py 2025-10-14 15:52:31.000000000 +0200 +++ Python-3.13.9/Doc/tools/extensions/pydoc_topics.py 2025-11-04 17:41:42.881251888 +0100 @@ -1,21 +1,23 @@ """Support for building "topic help" for pydoc.""" -from __future__ import annotations - from time import asctime -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Tuple from sphinx.builders.text import TextBuilder from sphinx.util import logging -from sphinx.util.display import status_iterator +try: + from sphinx.util.display import status_iterator +except ModuleNotFoundError: + from sphinx.util import status_iterator from sphinx.util.docutils import new_document from sphinx.writers.text import TextTranslator -if TYPE_CHECKING: +try: + from typing import Sequence, Set +except ModuleNotFoundError: from collections.abc import Sequence, Set - from sphinx.application import Sphinx - from sphinx.util.typing import ExtensionMetadata +from sphinx.application import Sphinx logger = logging.getLogger(__name__) @@ -162,7 +164,7 @@ self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8") -def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str: +def _display_labels(item: Tuple[str, Sequence[Tuple[str, str]]]) -> str: _docname, label_ids = item labels = [name for name, _id in label_ids] if len(labels) > 4: @@ -170,7 +172,7 @@ return ", ".join(labels) -def _repr(text: str, /) -> str: +def _repr(text: str) -> str: """Return a triple-single-quoted representation of text.""" if "'''" not in text: return f"r'''{text}'''" @@ -178,7 +180,7 @@ return f"'''{text}'''" -def setup(app: Sphinx) -> ExtensionMetadata: +def setup(app: Sphinx) -> "ExtensionMetadata": app.add_builder(PydocTopicsBuilder) return {