Skip to content

Commit

Permalink
[autodoc] add support for singledispatchmethod class methods (#11284)
Browse files Browse the repository at this point in the history
  • Loading branch information
picnixz authored Mar 14, 2024
1 parent dbb4da3 commit c86e908
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ Bugs fixed
* #11474: Fix doctrees caching causing files not be rebuilt in some cases,
e.g., when :confval:`numfig` is ``True``.
Patch by Bénédikt Tran.
* #11278: autodoc: Fix rendering of :class:`functools.singledispatchmethod`
combined with :func:`@classmethod <classmethod>`.
Patch by Bénédikt Tran.

Testing
-------
Expand Down
5 changes: 4 additions & 1 deletion sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2229,7 +2229,8 @@ 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):
if (inspect.isclassmethod(obj) or
inspect.is_singledispatch_method(obj) and inspect.isclassmethod(obj.func)):
self.add_line(' :classmethod:', sourcename)
if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name):
self.add_line(' :staticmethod:', sourcename)
Expand Down Expand Up @@ -2261,6 +2262,8 @@ def format_signature(self, **kwargs: Any) -> str:
if typ is object:
pass # default implementation. skipped.
else:
if inspect.isclassmethod(func):
func = func.__func__
dispatchmeth = self.annotate_to_first_argument(func, typ)
if dispatchmeth:
documenter = MethodDocumenter(self.directive, '')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from functools import singledispatchmethod


class Foo:
"""docstring"""

@singledispatchmethod
@classmethod
def class_meth(cls, arg, kwarg=None):
"""A class method for general use."""
pass

@class_meth.register(int)
@class_meth.register(float)
@classmethod
def _class_meth_int(cls, arg, kwarg=None):
"""A class method for numbers."""
pass

@class_meth.register(str)
@classmethod
def _class_meth_str(cls, arg, kwarg=None):
"""A class method for str."""
pass

@class_meth.register
@classmethod
def _class_meth_dict(cls, arg: dict, kwarg=None):
"""A class method for dict."""
# This function tests for specifying type through annotations
pass
49 changes: 49 additions & 0 deletions tests/test_extensions/test_ext_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2111,6 +2111,55 @@ def test_singledispatchmethod_automethod(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatchmethod_classmethod(app):
options = {"members": None}
actual = do_autodoc(app, 'module', 'target.singledispatchmethod_classmethod', options)

assert list(actual) == [
'',
'.. py:module:: target.singledispatchmethod_classmethod',
'',
'',
'.. py:class:: Foo()',
' :module: target.singledispatchmethod_classmethod',
'',
' docstring',
'',
'',
' .. py:method:: Foo.class_meth(arg, kwarg=None)',
' Foo.class_meth(arg: float, kwarg=None)',
' Foo.class_meth(arg: int, kwarg=None)',
' Foo.class_meth(arg: str, kwarg=None)',
' Foo.class_meth(arg: dict, kwarg=None)',
' :module: target.singledispatchmethod_classmethod',
' :classmethod:',
'',
' A class method for general use.',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatchmethod_classmethod_automethod(app):
options = {}
actual = do_autodoc(app, 'method', 'target.singledispatchmethod_classmethod.Foo.class_meth', options)

assert list(actual) == [
'',
'.. py:method:: Foo.class_meth(arg, kwarg=None)',
' Foo.class_meth(arg: float, kwarg=None)',
' Foo.class_meth(arg: int, kwarg=None)',
' Foo.class_meth(arg: str, kwarg=None)',
' Foo.class_meth(arg: dict, kwarg=None)',
' :module: target.singledispatchmethod_classmethod',
' :classmethod:',
'',
' A class method for general use.',
'',
]


@pytest.mark.skipif(sys.version_info[:2] >= (3, 13),
reason='Cython does not support Python 3.13 yet.')
@pytest.mark.skipif(pyximport is None, reason='cython is not installed')
Expand Down

0 comments on commit c86e908

Please sign in to comment.