From 09f624947e6ab8ce33f76b880516111d42762126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:35:18 +0100 Subject: [PATCH 01/13] fix detection of built-in class methods --- sphinx/ext/autodoc/__init__.py | 27 +++++++++++++++------------ sphinx/util/inspect.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 60c31e2542e..1a185c328e9 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -2202,16 +2202,17 @@ def format_args(self, **kwargs: Any) -> str: # But it makes users confused. args = '()' else: - if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): - self.env.events.emit( - 'autodoc-before-process-signature', self.object, False - ) - sig = inspect.signature(self.object, bound_method=False, - type_aliases=self.config.autodoc_type_aliases) - else: - self.env.events.emit('autodoc-before-process-signature', self.object, True) - sig = inspect.signature(self.object, bound_method=True, - type_aliases=self.config.autodoc_type_aliases) + bound_method = not inspect.isstaticmethod( + self.object, cls=self.parent, name=self.object_name, + ) + self.env.events.emit( + 'autodoc-before-process-signature', self.object, bound_method, + ) + sig = inspect.signature( + self.object, + bound_method=bound_method, + type_aliases=self.config.autodoc_type_aliases, + ) args = stringify_signature(sig, **kwargs) except TypeError as exc: logger.warning(__("Failed to get a method signature for %s: %s"), @@ -2234,8 +2235,10 @@ def add_directive_header(self, sig: str) -> None: self.add_line(' :abstractmethod:', sourcename) if inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj): self.add_line(' :async:', sourcename) - if (inspect.isclassmethod(obj) or - inspect.is_singledispatch_method(obj) and inspect.isclassmethod(obj.func)): + if ( + inspect.is_classmethod_like(obj) + or inspect.is_singledispatch_method(obj) and inspect.is_classmethod_like(obj.func) + ): self.add_line(' :classmethod:', sourcename) if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name): self.add_line(' :staticmethod:', sourcename) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index e2bc8b1e6a7..b14eb88bddb 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -241,6 +241,38 @@ def isclassmethod( return False +def isclassmethoddescriptor( + obj: Any, + cls: Any = None, + name: str | None = None, +) -> TypeIs[types.ClassMethodDescriptorType]: + """Check if the object is a class method descriptor type. + + This is typically useful to check if a built-in method is a class method. + """ + if isinstance(obj, types.ClassMethodDescriptorType): + return True + if cls and name: + # trace __mro__ if the method is defined in parent class + sentinel = object() + for basecls in getmro(cls): + meth = basecls.__dict__.get(name, sentinel) + if meth is not sentinel: + return isinstance(obj, types.ClassMethodDescriptorType) + return False + + +def is_classmethod_like( + obj: Any, + cls: Any = None, + name: str | None = None, +) -> TypeIs[classmethod | types.ClassMethodDescriptorType]: + """Check if the object behaves like a class method.""" + # Built-in methods are instances of ClassMethodDescriptorType + # while pure Python class methods are instances of classmethod(). + return isclassmethod(obj, cls, name) or isclassmethoddescriptor(obj, cls, name) + + def isstaticmethod( obj: Any, cls: Any = None, @@ -249,6 +281,8 @@ def isstaticmethod( """Check if the object is a :class:`staticmethod`.""" if isinstance(obj, staticmethod): return True + # Unlike built-in class methods, built-in static methods + # satisfy "isinstance(cls.__dict__[name], staticmethod)". if cls and name: # trace __mro__ if the method is defined in parent class sentinel = object() From fee7d108baf0c6866dad35de93d8b8719aa6c149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:37:29 +0100 Subject: [PATCH 02/13] fix signature of built-in methods --- sphinx/util/inspect.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index b14eb88bddb..a0a928e6d5a 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -679,7 +679,9 @@ def signature( ) -> Signature: """Return a Signature object for the given *subject*. - :param bound_method: Specify *subject* is a bound method or not + :param bound_method: Specify *subject* is a bound method or not. + + When *subject* is a built-in callable, *bound_method* is ignored. """ if type_aliases is None: type_aliases = {} @@ -715,7 +717,10 @@ def signature( # ForwardRef and so on. pass - if bound_method: + # For built-in objects, we use the signature that was specified + # by the extension module even if we detected the subject to be + # a possible bound method. + if bound_method and not inspect.isbuiltin(subject): if inspect.ismethod(subject): # ``inspect.signature()`` considers the subject is a bound method and removes # first argument from signature. Therefore no skips are needed here. From bcf738db31587e8c1d40a731db14e352cf35d165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 1 Jan 2025 10:28:14 +0100 Subject: [PATCH 03/13] update CHANGES.rst --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e20c1e4571e..c6bb532fc0c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,8 @@ Bugs fixed * LaTeX: fix a ``7.4.0`` typo in a default for ``\sphinxboxsetup`` (refs: PR #13152). Patch by Jean-François B. +* #13188: autodoc: fix detection of class methods implemented in C. + Patch by Bénédikt Tran. Testing ------- From 578c706724a39599e076ac8191c586bc522cbdd2 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 3 Jan 2025 07:10:08 +0000 Subject: [PATCH 04/13] post-merge --- sphinx/ext/autodoc/__init__.py | 35 +++++++++++++++++++++------------- sphinx/util/inspect.py | 22 +++++++-------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index de084bd6c70..0b6e044cb58 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -2385,17 +2385,26 @@ def format_args(self, **kwargs: Any) -> str: # But it makes users confused. args = '()' else: - bound_method = not inspect.isstaticmethod( - self.object, cls=self.parent, name=self.object_name, - ) - self.env.events.emit( - 'autodoc-before-process-signature', self.object, bound_method, - ) - sig = inspect.signature( - self.object, - bound_method=bound_method, - type_aliases=self.config.autodoc_type_aliases, - ) + if inspect.isstaticmethod( + self.object, cls=self.parent, name=self.object_name + ): + self.env.events.emit( + 'autodoc-before-process-signature', self.object, False + ) + sig = inspect.signature( + self.object, + bound_method=False, + type_aliases=self.config.autodoc_type_aliases, + ) + else: + self.env.events.emit( + 'autodoc-before-process-signature', self.object, True + ) + sig = inspect.signature( + self.object, + bound_method=True, + type_aliases=self.config.autodoc_type_aliases, + ) args = stringify_signature(sig, **kwargs) except TypeError as exc: logger.warning( @@ -2420,9 +2429,9 @@ def add_directive_header(self, sig: str) -> None: if inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj): self.add_line(' :async:', sourcename) if ( - inspect.is_classmethod_like(obj) + inspect.is_class_method_like(obj) or inspect.is_singledispatch_method(obj) - and inspect.is_classmethod_like(obj.func) + and inspect.is_class_method_like(obj.func) ): self.add_line(' :classmethod:', sourcename) if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name): diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 676ee09ab99..6ef62cb9359 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -222,9 +222,7 @@ def ispartial(obj: Any) -> TypeIs[partial | partialmethod]: def isclassmethod( - obj: Any, - cls: Any = None, - name: str | None = None, + obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[classmethod]: """Check if the object is a :class:`classmethod`.""" if isinstance(obj, classmethod): @@ -241,10 +239,8 @@ def isclassmethod( return False -def isclassmethoddescriptor( - obj: Any, - cls: Any = None, - name: str | None = None, +def is_class_method_descriptor( + obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[types.ClassMethodDescriptorType]: """Check if the object is a class method descriptor type. @@ -262,21 +258,17 @@ def isclassmethoddescriptor( return False -def is_classmethod_like( - obj: Any, - cls: Any = None, - name: str | None = None, +def is_class_method_like( + obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[classmethod | types.ClassMethodDescriptorType]: """Check if the object behaves like a class method.""" # Built-in methods are instances of ClassMethodDescriptorType # while pure Python class methods are instances of classmethod(). - return isclassmethod(obj, cls, name) or isclassmethoddescriptor(obj, cls, name) + return isclassmethod(obj, cls, name) or is_class_method_descriptor(obj, cls, name) def isstaticmethod( - obj: Any, - cls: Any = None, - name: str | None = None, + obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[staticmethod]: """Check if the object is a :class:`staticmethod`.""" if isinstance(obj, staticmethod): From c85f667b2af66e9ae1e250cf77bb1f4203a1a186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:24:05 +0100 Subject: [PATCH 05/13] address Adam's review --- sphinx/ext/autodoc/__init__.py | 4 ++-- sphinx/util/inspect.py | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 0b6e044cb58..f6c1505ef82 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -2429,9 +2429,9 @@ def add_directive_header(self, sig: str) -> None: if inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj): self.add_line(' :async:', sourcename) if ( - inspect.is_class_method_like(obj) + inspect.is_classmethod_like(obj) or inspect.is_singledispatch_method(obj) - and inspect.is_class_method_like(obj.func) + and inspect.is_classmethod_like(obj.func) ): self.add_line(' :classmethod:', sourcename) if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name): diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 6ef62cb9359..a59bdfadf36 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -239,7 +239,7 @@ def isclassmethod( return False -def is_class_method_descriptor( +def is_classmethod_descriptor( obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[types.ClassMethodDescriptorType]: """Check if the object is a class method descriptor type. @@ -258,13 +258,13 @@ def is_class_method_descriptor( return False -def is_class_method_like( +def is_classmethod_like( obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[classmethod | types.ClassMethodDescriptorType]: """Check if the object behaves like a class method.""" # Built-in methods are instances of ClassMethodDescriptorType # while pure Python class methods are instances of classmethod(). - return isclassmethod(obj, cls, name) or is_class_method_descriptor(obj, cls, name) + return isclassmethod(obj, cls, name) or is_classmethod_descriptor(obj, cls, name) def isstaticmethod( @@ -964,11 +964,15 @@ def getdoc( * inherited docstring * inherited decorated methods """ - if cls and name and isclassmethod(obj, cls, name): + if cls and name and is_classmethod_like(obj, cls, name): for basecls in getmro(cls): meth = basecls.__dict__.get(name) - if meth and hasattr(meth, '__func__'): - doc: str | None = getdoc(meth.__func__) + if not meth: + continue + # Built-in class methods do not have '__func__' + # but they may have a docstring. + if hasattr(meth, '__func__') or is_classmethod_descriptor(meth): + doc: str | None = getdoc(getattr(meth, '__func__', meth)) if doc is not None or not allow_inherited: return doc From bf164f1cb47979e6be7172f23c9995bbb666c88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:37:52 +0100 Subject: [PATCH 06/13] fix tests and detection --- sphinx/util/inspect.py | 23 +++++++++++++++++++++-- tests/test_util/test_util_inspect.py | 26 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index a59bdfadf36..72322bf9c32 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -254,17 +254,36 @@ def is_classmethod_descriptor( for basecls in getmro(cls): meth = basecls.__dict__.get(name, sentinel) if meth is not sentinel: - return isinstance(obj, types.ClassMethodDescriptorType) + return is_classmethod_descriptor(obj) return False +def is_native_classmethod(obj: Any, cls: Any = None, name: str | None = None): + """Check if the object is a class method implemented in C.""" + if is_classmethod_descriptor(obj, cls, name): + return True + if ( + isbuiltin(obj) + and getattr(obj, '__self__', None) is not None + and isclass(obj.__self__) + ): + return True + if cls and name: + # trace __mro__ if the method is defined in parent class + sentinel = object() + for basecls in getmro(cls): + meth = basecls.__dict__.get(name, sentinel) + if meth is not sentinel: + return is_native_classmethod(obj) + return False + def is_classmethod_like( obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[classmethod | types.ClassMethodDescriptorType]: """Check if the object behaves like a class method.""" # Built-in methods are instances of ClassMethodDescriptorType # while pure Python class methods are instances of classmethod(). - return isclassmethod(obj, cls, name) or is_classmethod_descriptor(obj, cls, name) + return isclassmethod(obj, cls, name) or is_native_classmethod(obj, cls, name) def isstaticmethod( diff --git a/tests/test_util/test_util_inspect.py b/tests/test_util/test_util_inspect.py index f80e258490a..498eb79f0f1 100644 --- a/tests/test_util/test_util_inspect.py +++ b/tests/test_util/test_util_inspect.py @@ -703,6 +703,32 @@ def test_isclassmethod(): assert not inspect.isclassmethod(Inherited.meth) +def test_is_classmethod_descriptor(): + assert inspect.is_classmethod_descriptor(int.__dict__['from_bytes']) + assert inspect.is_classmethod_descriptor(bytes.__dict__['fromhex']) + + assert not inspect.is_classmethod_descriptor(int.from_bytes) + assert not inspect.is_classmethod_descriptor(bytes.fromhex) + + assert not inspect.is_classmethod_descriptor(int.__init__) + assert not inspect.is_classmethod_descriptor(int.bit_count) + + assert not inspect.is_classmethod_descriptor(Base.classmeth) + assert not inspect.is_classmethod_descriptor(Inherited.classmeth) + + +def test_is_classmethod_like(): + assert inspect.is_classmethod_like(Base.classmeth) + assert not inspect.is_classmethod_like(Base.meth) + assert inspect.is_classmethod_like(Inherited.classmeth) + assert not inspect.is_classmethod_like(Inherited.meth) + + assert inspect.is_classmethod_like(int.from_bytes) + assert inspect.is_classmethod_like(bytes.fromhex) + assert not inspect.is_classmethod_like(int.__init__) + assert not inspect.is_classmethod_like(int.bit_count) + + def test_isstaticmethod(): assert inspect.isstaticmethod(Base.staticmeth, Base, 'staticmeth') assert not inspect.isstaticmethod(Base.meth, Base, 'meth') From e9cd4bf7b0b094c7fd2556b9596d9230987e657c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:42:34 +0100 Subject: [PATCH 07/13] fix lint (manually) --- sphinx/util/inspect.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 72322bf9c32..3f396535dde 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -258,7 +258,9 @@ def is_classmethod_descriptor( return False -def is_native_classmethod(obj: Any, cls: Any = None, name: str | None = None): +def is_builtin_classmethod( + obj: Any, cls: Any = None, name: str | None = None +) -> bool: """Check if the object is a class method implemented in C.""" if is_classmethod_descriptor(obj, cls, name): return True @@ -274,16 +276,14 @@ def is_native_classmethod(obj: Any, cls: Any = None, name: str | None = None): for basecls in getmro(cls): meth = basecls.__dict__.get(name, sentinel) if meth is not sentinel: - return is_native_classmethod(obj) + return is_builtin_classmethod(obj) return False def is_classmethod_like( obj: Any, cls: Any = None, name: str | None = None -) -> TypeIs[classmethod | types.ClassMethodDescriptorType]: +) -> bool: """Check if the object behaves like a class method.""" - # Built-in methods are instances of ClassMethodDescriptorType - # while pure Python class methods are instances of classmethod(). - return isclassmethod(obj, cls, name) or is_native_classmethod(obj, cls, name) + return isclassmethod(obj, cls, name) or is_builtin_classmethod(obj, cls, name) def isstaticmethod( From 30a6f82caade8143bd59e49619ae2bce7ccaf65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:43:24 +0100 Subject: [PATCH 08/13] fix lint --- sphinx/util/inspect.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 3f396535dde..0b1c0c0413f 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -258,9 +258,7 @@ def is_classmethod_descriptor( return False -def is_builtin_classmethod( - obj: Any, cls: Any = None, name: str | None = None -) -> bool: +def is_builtin_classmethod(obj: Any, cls: Any = None, name: str | None = None) -> bool: """Check if the object is a class method implemented in C.""" if is_classmethod_descriptor(obj, cls, name): return True @@ -279,9 +277,8 @@ def is_builtin_classmethod( return is_builtin_classmethod(obj) return False -def is_classmethod_like( - obj: Any, cls: Any = None, name: str | None = None -) -> bool: + +def is_classmethod_like(obj: Any, cls: Any = None, name: str | None = None) -> bool: """Check if the object behaves like a class method.""" return isclassmethod(obj, cls, name) or is_builtin_classmethod(obj, cls, name) From 823dabe86823e7063d1544a76416155ea8c85732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 22:30:59 +0100 Subject: [PATCH 09/13] Update sphinx/util/inspect.py Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- sphinx/util/inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 0b1c0c0413f..b724c828117 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -242,7 +242,7 @@ def isclassmethod( def is_classmethod_descriptor( obj: Any, cls: Any = None, name: str | None = None ) -> TypeIs[types.ClassMethodDescriptorType]: - """Check if the object is a class method descriptor type. + """Check if the object is a :class:`~types.ClassMethodDescriptorType`. This is typically useful to check if a built-in method is a class method. """ From c65477e9df44be197e86c29171bbb466bf590bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:31:07 +0100 Subject: [PATCH 10/13] fix --- sphinx/util/inspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index b724c828117..5790a18a6b3 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -254,7 +254,7 @@ def is_classmethod_descriptor( for basecls in getmro(cls): meth = basecls.__dict__.get(name, sentinel) if meth is not sentinel: - return is_classmethod_descriptor(obj) + return isinstance(meth, types.ClassMethodDescriptorType) return False @@ -274,7 +274,7 @@ def is_builtin_classmethod(obj: Any, cls: Any = None, name: str | None = None) - for basecls in getmro(cls): meth = basecls.__dict__.get(name, sentinel) if meth is not sentinel: - return is_builtin_classmethod(obj) + return is_builtin_classmethod(meth) return False From 67899ffe7d8210b3f27d76a38dade56e4cef04b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:03:08 +0100 Subject: [PATCH 11/13] Update sphinx/util/inspect.py Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- sphinx/util/inspect.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 5790a18a6b3..0e31a50d28a 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -274,7 +274,11 @@ def is_builtin_classmethod(obj: Any, cls: Any = None, name: str | None = None) - for basecls in getmro(cls): meth = basecls.__dict__.get(name, sentinel) if meth is not sentinel: - return is_builtin_classmethod(meth) + return is_classmethod_descriptor(obj, cls, name) or ( + isbuiltin(obj) + and getattr(obj, '__self__', None) is not None + and isclass(obj.__self__) + ) return False From a0208231370b1854564fe4f0bfc8dd3cf9790627 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:30:07 +0000 Subject: [PATCH 12/13] Update sphinx/util/inspect.py --- sphinx/util/inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 0e31a50d28a..964ed948f08 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -274,7 +274,7 @@ def is_builtin_classmethod(obj: Any, cls: Any = None, name: str | None = None) - for basecls in getmro(cls): meth = basecls.__dict__.get(name, sentinel) if meth is not sentinel: - return is_classmethod_descriptor(obj, cls, name) or ( + return is_classmethod_descriptor(obj, meth, name) or ( isbuiltin(obj) and getattr(obj, '__self__', None) is not None and isclass(obj.__self__) From 7d411f07c02a517dea9d0e0998cd651ea6fe290d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:34:00 +0000 Subject: [PATCH 13/13] Fix mro check for is_classmethod_descriptor --- sphinx/util/inspect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 964ed948f08..9958180aa0a 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -274,10 +274,10 @@ def is_builtin_classmethod(obj: Any, cls: Any = None, name: str | None = None) - for basecls in getmro(cls): meth = basecls.__dict__.get(name, sentinel) if meth is not sentinel: - return is_classmethod_descriptor(obj, meth, name) or ( - isbuiltin(obj) - and getattr(obj, '__self__', None) is not None - and isclass(obj.__self__) + return is_classmethod_descriptor(meth, None, None) or ( + isbuiltin(meth) + and getattr(meth, '__self__', None) is not None + and isclass(meth.__self__) ) return False