Skip to content

Commit c86e908

Browse files
authored
[autodoc] add support for singledispatchmethod class methods (#11284)
1 parent dbb4da3 commit c86e908

File tree

4 files changed

+87
-1
lines changed

4 files changed

+87
-1
lines changed

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ Bugs fixed
9393
* #11474: Fix doctrees caching causing files not be rebuilt in some cases,
9494
e.g., when :confval:`numfig` is ``True``.
9595
Patch by Bénédikt Tran.
96+
* #11278: autodoc: Fix rendering of :class:`functools.singledispatchmethod`
97+
combined with :func:`@classmethod <classmethod>`.
98+
Patch by Bénédikt Tran.
9699

97100
Testing
98101
-------

sphinx/ext/autodoc/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2229,7 +2229,8 @@ def add_directive_header(self, sig: str) -> None:
22292229
self.add_line(' :abstractmethod:', sourcename)
22302230
if inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj):
22312231
self.add_line(' :async:', sourcename)
2232-
if inspect.isclassmethod(obj):
2232+
if (inspect.isclassmethod(obj) or
2233+
inspect.is_singledispatch_method(obj) and inspect.isclassmethod(obj.func)):
22332234
self.add_line(' :classmethod:', sourcename)
22342235
if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name):
22352236
self.add_line(' :staticmethod:', sourcename)
@@ -2261,6 +2262,8 @@ def format_signature(self, **kwargs: Any) -> str:
22612262
if typ is object:
22622263
pass # default implementation. skipped.
22632264
else:
2265+
if inspect.isclassmethod(func):
2266+
func = func.__func__
22642267
dispatchmeth = self.annotate_to_first_argument(func, typ)
22652268
if dispatchmeth:
22662269
documenter = MethodDocumenter(self.directive, '')
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from functools import singledispatchmethod
2+
3+
4+
class Foo:
5+
"""docstring"""
6+
7+
@singledispatchmethod
8+
@classmethod
9+
def class_meth(cls, arg, kwarg=None):
10+
"""A class method for general use."""
11+
pass
12+
13+
@class_meth.register(int)
14+
@class_meth.register(float)
15+
@classmethod
16+
def _class_meth_int(cls, arg, kwarg=None):
17+
"""A class method for numbers."""
18+
pass
19+
20+
@class_meth.register(str)
21+
@classmethod
22+
def _class_meth_str(cls, arg, kwarg=None):
23+
"""A class method for str."""
24+
pass
25+
26+
@class_meth.register
27+
@classmethod
28+
def _class_meth_dict(cls, arg: dict, kwarg=None):
29+
"""A class method for dict."""
30+
# This function tests for specifying type through annotations
31+
pass

tests/test_extensions/test_ext_autodoc.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,55 @@ def test_singledispatchmethod_automethod(app):
21112111
]
21122112

21132113

2114+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
2115+
def test_singledispatchmethod_classmethod(app):
2116+
options = {"members": None}
2117+
actual = do_autodoc(app, 'module', 'target.singledispatchmethod_classmethod', options)
2118+
2119+
assert list(actual) == [
2120+
'',
2121+
'.. py:module:: target.singledispatchmethod_classmethod',
2122+
'',
2123+
'',
2124+
'.. py:class:: Foo()',
2125+
' :module: target.singledispatchmethod_classmethod',
2126+
'',
2127+
' docstring',
2128+
'',
2129+
'',
2130+
' .. py:method:: Foo.class_meth(arg, kwarg=None)',
2131+
' Foo.class_meth(arg: float, kwarg=None)',
2132+
' Foo.class_meth(arg: int, kwarg=None)',
2133+
' Foo.class_meth(arg: str, kwarg=None)',
2134+
' Foo.class_meth(arg: dict, kwarg=None)',
2135+
' :module: target.singledispatchmethod_classmethod',
2136+
' :classmethod:',
2137+
'',
2138+
' A class method for general use.',
2139+
'',
2140+
]
2141+
2142+
2143+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
2144+
def test_singledispatchmethod_classmethod_automethod(app):
2145+
options = {}
2146+
actual = do_autodoc(app, 'method', 'target.singledispatchmethod_classmethod.Foo.class_meth', options)
2147+
2148+
assert list(actual) == [
2149+
'',
2150+
'.. py:method:: Foo.class_meth(arg, kwarg=None)',
2151+
' Foo.class_meth(arg: float, kwarg=None)',
2152+
' Foo.class_meth(arg: int, kwarg=None)',
2153+
' Foo.class_meth(arg: str, kwarg=None)',
2154+
' Foo.class_meth(arg: dict, kwarg=None)',
2155+
' :module: target.singledispatchmethod_classmethod',
2156+
' :classmethod:',
2157+
'',
2158+
' A class method for general use.',
2159+
'',
2160+
]
2161+
2162+
21142163
@pytest.mark.skipif(sys.version_info[:2] >= (3, 13),
21152164
reason='Cython does not support Python 3.13 yet.')
21162165
@pytest.mark.skipif(pyximport is None, reason='cython is not installed')

0 commit comments

Comments
 (0)