From aad8750d1dcc83e11048f77e42a2a4115e3e0cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Thu, 5 Sep 2024 11:42:09 +1200 Subject: [PATCH 1/5] add py3.9 compatibility section to sage_autodoc.py --- src/sage_docbuild/ext/sage_autodoc.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 220067f125f..68596af4401 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -1903,6 +1903,28 @@ def get_doc(self) -> list[list[str]] | None: if isinstance(self.object, TypeVar): if self.object.__doc__ == TypeVar.__doc__: return [] + # ------------------------------------------------------------------ + # This section is kept for compatibility with python 3.9 + # see https://github.com/sagemath/sage/pull/38549#issuecomment-2327790930 + if sys.version_info[:2] < (3, 10): + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + parts = self.modname.strip('.').split('.') + orig_objpath = self.objpath + for i in range(len(parts)): + new_modname = '.'.join(parts[:len(parts) - i]) + new_objpath = parts[len(parts) - i:] + orig_objpath + try: + analyzer = ModuleAnalyzer.for_module(new_modname) + analyzer.analyze() + key = ('', new_objpath[-1]) + comment = list(analyzer.attr_docs.get(key, [])) + if comment: + self.objpath = new_objpath + self.modname = new_modname + return [comment] + except PycodeError: + pass + # ------------------------------------------------------------------ if self.doc_as_attr: # Don't show the docstring of the class when it is an alias. if self.get_variable_comment(): From 46b390d9d0493d32f4e03b8a87b29f8c2e09a2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Thu, 5 Sep 2024 13:59:20 +1200 Subject: [PATCH 2/5] import sys as it is needed to select py3.9 code --- src/sage_docbuild/ext/sage_autodoc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 68596af4401..d874ba0b59c 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -39,6 +39,7 @@ import functools import operator +import sys import re from inspect import Parameter, Signature from typing import TYPE_CHECKING, Any, ClassVar, NewType, TypeVar From fec9419ea1151999d9ffbc4c1205d478d230b1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Fri, 6 Sep 2024 10:18:15 +1200 Subject: [PATCH 3/5] more py3.9 corrections --- src/sage_docbuild/ext/sage_autodoc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index d874ba0b59c..7351a475331 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -1578,7 +1578,7 @@ def can_document_member( cls: type[Documenter], member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: return isinstance(member, type) or ( - isattr and isinstance(member, NewType | TypeVar)) + isattr and (inspect.isNewType(member) or isinstance(member, TypeVar))) def import_object(self, raiseerror: bool = False) -> bool: ret = super().import_object(raiseerror) @@ -1651,7 +1651,7 @@ def import_object(self, raiseerror: bool = False) -> bool: # ------------------------------------------------------------------- else: self.doc_as_attr = True - if isinstance(self.object, NewType | TypeVar): + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): modname = getattr(self.object, '__module__', self.modname) if modname != self.modname and self.modname.startswith(modname): bases = self.modname[len(modname):].strip('.').split('.') @@ -1660,7 +1660,7 @@ def import_object(self, raiseerror: bool = False) -> bool: return ret def _get_signature(self) -> tuple[Any | None, str | None, Signature | None]: - if isinstance(self.object, NewType | TypeVar): + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): # Suppress signature return None, None, None @@ -1845,14 +1845,14 @@ def add_directive_header(self, sig: str) -> None: self.directivetype = 'attribute' super().add_directive_header(sig) - if isinstance(self.object, NewType | TypeVar): + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): return if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(' :final:', sourcename) canonical_fullname = self.get_canonical_fullname() - if (not self.doc_as_attr and not isinstance(self.object, NewType) + if (not self.doc_as_attr and not inspect.isNewType(self.object) and canonical_fullname and self.fullname != canonical_fullname): self.add_line(' :canonical: %s' % canonical_fullname, sourcename) @@ -1989,7 +1989,7 @@ def get_variable_comment(self) -> list[str] | None: return None def add_content(self, more_content: StringList | None) -> None: - if isinstance(self.object, NewType): + if inspect.isNewType(self.object): if self.config.autodoc_typehints_format == "short": supertype = restify(self.object.__supertype__, "smart") else: From f003cc9d22a05fa467548450d15c0d6cbec1f242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Fri, 6 Sep 2024 12:33:38 +1200 Subject: [PATCH 4/5] py3.9 compatibility for zip --- src/sage_docbuild/ext/sage_autodoc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 7351a475331..0cc04e776d8 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -671,7 +671,7 @@ def add_content(self, more_content: StringList | None) -> None: # add additional content (e.g. from document), if present if more_content: - for line, src in zip(more_content.data, more_content.items, strict=True): + for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]: @@ -1042,7 +1042,7 @@ def add_content(self, more_content: StringList | None) -> None: super().add_content(None) self.indent = old_indent if more_content: - for line, src in zip(more_content.data, more_content.items, strict=True): + for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) @classmethod From 57f5d169486831d7213efc4208cae970d919486b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Tue, 10 Sep 2024 09:24:12 +1200 Subject: [PATCH 5/5] add try blocks to support both sphinx8 and py3.9 --- src/sage_docbuild/ext/sage_autodoc.py | 47 +++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 0cc04e776d8..87e4e69d7bd 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -33,6 +33,8 @@ - Kwankyu Lee (2024-02-14): rebased on Sphinx 7.2.6 - François Bissey (2024-08-24): rebased on Sphinx 8.0.2 + +- François Bissey (2024-09-10): Tweaks to support python 3.9 (and older sphinx) as well """ from __future__ import annotations @@ -1577,8 +1579,14 @@ def __init__(self, *args: Any) -> None: def can_document_member( cls: type[Documenter], member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: - return isinstance(member, type) or ( - isattr and (inspect.isNewType(member) or isinstance(member, TypeVar))) + # support both sphinx 8 and py3.9/older sphinx + try: + result_bool = isinstance(member, type) or ( + isattr and isinstance(member, NewType | TypeVar)) + except: + result_bool = isinstance(member, type) or ( + isattr and (inspect.isNewType(member) or isinstance(member, TypeVar))) + return result_bool def import_object(self, raiseerror: bool = False) -> bool: ret = super().import_object(raiseerror) @@ -1651,7 +1659,12 @@ def import_object(self, raiseerror: bool = False) -> bool: # ------------------------------------------------------------------- else: self.doc_as_attr = True - if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + # support both sphinx 8 and py3.9/older sphinx + try: + test_bool = isinstance(self.object, NewType | TypeVar) + except: + test_bool = inspect.isNewType(self.object) or isinstance(self.object, TypeVar) + if test_bool: modname = getattr(self.object, '__module__', self.modname) if modname != self.modname and self.modname.startswith(modname): bases = self.modname[len(modname):].strip('.').split('.') @@ -1660,7 +1673,12 @@ def import_object(self, raiseerror: bool = False) -> bool: return ret def _get_signature(self) -> tuple[Any | None, str | None, Signature | None]: - if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + # support both sphinx 8 and py3.9/older sphinx + try: + test_bool = isinstance(self.object, NewType | TypeVar) + except: + test_bool = inspect.isNewType(self.object) or isinstance(self.object, TypeVar) + if test_bool: # Suppress signature return None, None, None @@ -1845,14 +1863,24 @@ def add_directive_header(self, sig: str) -> None: self.directivetype = 'attribute' super().add_directive_header(sig) - if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + # support both sphinx 8 and py3.9/older sphinx + try: + test_bool = isinstance(self.object, NewType | TypeVar) + except: + test_bool = inspect.isNewType(self.object) or isinstance(self.object, TypeVar) + if test_bool: return if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(' :final:', sourcename) canonical_fullname = self.get_canonical_fullname() - if (not self.doc_as_attr and not inspect.isNewType(self.object) + # support both sphinx 8 and py3.9/older sphinx + try: + newtype_test = isinstance(self.object, NewType) + except: + newtype_test = inspect.isNewType(self.object) + if (not self.doc_as_attr and not newtype_test and canonical_fullname and self.fullname != canonical_fullname): self.add_line(' :canonical: %s' % canonical_fullname, sourcename) @@ -1989,7 +2017,12 @@ def get_variable_comment(self) -> list[str] | None: return None def add_content(self, more_content: StringList | None) -> None: - if inspect.isNewType(self.object): + # support both sphinx 8 and py3.9/older sphinx + try: + newtype_test = isinstance(self.object, NewType) + except: + newtype_test = inspect.isNewType(self.object) + if newtype_test: if self.config.autodoc_typehints_format == "short": supertype = restify(self.object.__supertype__, "smart") else: