From c75ef22ae56384ad55d4377db151337af0f015d6591d76cfd4397373e2494148 Mon Sep 17 00:00:00 2001
From: Matej Cepl <mcepl@suse.com>
Date: Fri, 13 Sep 2024 17:10:18 +0000
Subject: [PATCH] - Add doc-py38-to-py36.patch making building documentation  
 compatible with Python 3.6, which runs Sphinx on SLE.

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python312?expand=0&rev=67
---
 doc-py38-to-py36.patch | 374 +++++++++++++++++++++++++++++++++++++++++
 python312.changes      |   6 +
 python312.spec         |   8 +-
 3 files changed, 387 insertions(+), 1 deletion(-)
 create mode 100644 doc-py38-to-py36.patch

diff --git a/doc-py38-to-py36.patch b/doc-py38-to-py36.patch
new file mode 100644
index 0000000..0c25318
--- /dev/null
+++ b/doc-py38-to-py36.patch
@@ -0,0 +1,374 @@
+---
+ Doc/conf.py                             |    4 +-
+ Doc/tools/check-warnings.py             |    3 +
+ Doc/tools/extensions/audit_events.py    |   54 ++++++++++++++++----------------
+ Doc/tools/extensions/c_annotations.py   |   33 ++++++++-----------
+ Doc/tools/extensions/glossary_search.py |   10 +----
+ Doc/tools/extensions/patchlevel.py      |    9 ++---
+ 6 files changed, 55 insertions(+), 58 deletions(-)
+
+--- a/Doc/conf.py
++++ b/Doc/conf.py
+@@ -76,7 +76,7 @@ today_fmt = '%B %d, %Y'
+ highlight_language = 'python3'
+ 
+ # Minimum version of sphinx required
+-needs_sphinx = '6.2.1'
++needs_sphinx = '4.2.0'
+ 
+ # Create table of contents entries for domain objects (e.g. functions, classes,
+ # attributes, etc.). Default is True.
+@@ -328,7 +328,7 @@ html_short_title = f'{release} Documenta
+ # (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,
+--- a/Doc/tools/check-warnings.py
++++ b/Doc/tools/check-warnings.py
+@@ -228,7 +228,8 @@ def fail_if_regression(
+             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
+--- a/Doc/tools/extensions/audit_events.py
++++ b/Doc/tools/extensions/audit_events.py
+@@ -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,11 @@ from sphinx.transforms.post_transforms i
+ from sphinx.util import logging
+ from sphinx.util.docutils import SphinxDirective
+ 
+-if TYPE_CHECKING:
+-    from collections.abc import Iterator
++from typing import Any, List, Tuple
+ 
+-    from sphinx.application import Sphinx
+-    from sphinx.builders import Builder
+-    from sphinx.environment import BuildEnvironment
++from sphinx.application import Sphinx
++from sphinx.builders import Builder
++from sphinx.environment import BuildEnvironment
+ 
+ logger = logging.getLogger(__name__)
+ 
+@@ -32,16 +28,16 @@ _SYNONYMS = [
+ 
+ class AuditEvents:
+     def __init__(self) -> None:
+-        self.events: dict[str, list[str]] = {}
+-        self.sources: dict[str, list[tuple[str, str]]] = {}
++        self.events: dict[str, List[str]] = {}
++        self.sources: dict[str, List[Tuple[str, str]]] = {}
+ 
+-    def __iter__(self) -> Iterator[tuple[str, list[str], tuple[str, str]]]:
++    def __iter__(self) -> 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 +45,7 @@ class AuditEvents:
+             self.events[name] = args
+         self.sources.setdefault(name, []).append(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 +56,7 @@ class AuditEvents:
+         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 +69,7 @@ class AuditEvents:
+         name_clean = re.sub(r"\W", "_", name)
+         return f"audit_event_{name_clean}_{source_count}"
+ 
+-    def rows(self) -> Iterator[tuple[str, list[str], list[tuple[str, str]]]]:
++    def rows(self) -> Any:
+         for name in sorted(self.events.keys()):
+             yield name, self.events[name], self.sources[name]
+ 
+@@ -97,7 +93,7 @@ def audit_events_purge(
+ 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 +122,16 @@ class AuditEvent(SphinxDirective):
+         ),
+     ]
+ 
+-    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 +167,7 @@ class audit_event_list(nodes.General, no
+ 
+ 
+ class AuditEventListDirective(SphinxDirective):
+-    def run(self) -> list[audit_event_list]:
++    def run(self) -> List[audit_event_list]:
+         return [audit_event_list()]
+ 
+ 
+@@ -181,7 +179,11 @@ class AuditEventListTransform(SphinxPost
+             return
+ 
+         table = self._make_table(self.app.builder, self.env.docname)
+-        for node in self.document.findall(audit_event_list):
++        try:
++            findall = self.document.findall
++        except AttributeError:
++            findall = self.document.traverse
++        for node in findall(audit_event_list):
+             node.replace_self(table)
+ 
+     def _make_table(self, builder: Builder, docname: str) -> nodes.table:
+@@ -217,8 +219,8 @@ class AuditEventListTransform(SphinxPost
+         builder: Builder,
+         docname: str,
+         name: str,
+-        args: list[str],
+-        sources: list[tuple[str, str]],
++        args: List[str],
++        sources: List[Tuple[str, str]],
+     ) -> nodes.row:
+         row = nodes.row()
+         name_node = nodes.paragraph("", nodes.Text(name))
+--- a/Doc/tools/extensions/c_annotations.py
++++ b/Doc/tools/extensions/c_annotations.py
+@@ -9,12 +9,10 @@ Configuration:
+ * 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
+ 
+ import sphinx
+ from docutils import nodes
+@@ -23,9 +21,7 @@ from sphinx import addnodes
+ 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
+ 
+ ROLE_TO_OBJECT_TYPE = {
+     "func": "function",
+@@ -36,20 +32,20 @@ ROLE_TO_OBJECT_TYPE = {
+ }
+ 
+ 
+-@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.
+@@ -68,7 +64,7 @@ class StableABIEntry:
+     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():
+@@ -104,7 +100,7 @@ def read_refcount_data(refcount_filename
+     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):
+@@ -135,7 +131,8 @@ def add_annotations(app: Sphinx, doctree
+         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}: "
+@@ -242,7 +239,7 @@ def _unstable_api_annotation() -> nodes.
+     )
+ 
+ 
+-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.")
+@@ -262,7 +259,7 @@ class LimitedAPIList(SphinxDirective):
+     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}`"
+@@ -285,7 +282,7 @@ def init_annotations(app: Sphinx) -> Non
+     )
+ 
+ 
+-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)
+@@ -297,10 +294,10 @@ def setup(app: Sphinx) -> ExtensionMetad
+         from sphinx.domains.c import CObject
+ 
+         # monkey-patch C object...
+-        CObject.option_spec |= {
++        CObject.option_spec.update({
+             "no-index-entry": directives.flag,
+             "no-contents-entry": directives.flag,
+-        }
++        })
+ 
+     return {
+         "version": "1.0",
+--- a/Doc/tools/extensions/glossary_search.py
++++ b/Doc/tools/extensions/glossary_search.py
+@@ -1,18 +1,14 @@
+ """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__)
+ 
+@@ -60,7 +56,7 @@ def write_glossary_json(app: Sphinx, _ex
+     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)
+ 
+--- a/Doc/tools/extensions/patchlevel.py
++++ b/Doc/tools/extensions/patchlevel.py
+@@ -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 @@ class version_info(NamedTuple):  # noqa:
+     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 @@ def get_header_version_info() -> version
+     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 get_header_version_info() -> version
+     )
+ 
+ 
+-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":
diff --git a/python312.changes b/python312.changes
index 224d65e..211f3fc 100644
--- a/python312.changes
+++ b/python312.changes
@@ -1,3 +1,9 @@
+-------------------------------------------------------------------
+Fri Sep 13 17:09:37 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
+
+- Add doc-py38-to-py36.patch making building documentation
+  compatible with Python 3.6, which runs Sphinx on SLE.
+
 -------------------------------------------------------------------
 Sat Sep  7 21:49:34 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
 
diff --git a/python312.spec b/python312.spec
index 83404e6..55fc1b1 100644
--- a/python312.spec
+++ b/python312.spec
@@ -179,6 +179,9 @@ Patch40:        fix-test-recursion-limit-15.6.patch
 # PATCH-FIX-SLE docs-docutils_014-Sphinx_420.patch bsc#[0-9]+ mcepl@suse.com
 # related to gh#python/cpython#119317
 Patch41:        docs-docutils_014-Sphinx_420.patch
+# PATCH-FIX-SLE doc-py38-to-py36.patch mcepl@suse.com
+# Make documentation extensions working with Python 3.6
+Patch44:        doc-py38-to-py36.patch
 BuildRequires:  autoconf-archive
 BuildRequires:  automake
 BuildRequires:  fdupes
@@ -209,6 +212,9 @@ BuildRequires:  mpdecimal-devel
 BuildRequires:  python3-Sphinx >= 4.0.0
 %if 0%{?suse_version} >= 1500
 BuildRequires:  python3-python-docs-theme >= 2022.1
+%if 0%{?suse_version} < 1599
+BuildRequires:  python3-dataclasses
+%endif
 %endif
 %endif
 %if %{with general}
@@ -470,7 +476,7 @@ rm Lib/site-packages/README.txt
 tar xvf %{SOURCE21}
 
 # Don't fail on warnings when building documentation
-# sed -i -e '/^SPHINXERRORHANDLING/s/-W//' Doc/Makefile
+sed -i -e '/^SPHINXERRORHANDLING/s/-W//' Doc/Makefile
 
 %build
 %if %{with doc}