From 22fafb9773e3e78b05072909c019d70545cf9226 Mon Sep 17 00:00:00 2001 From: Tom Cobb Date: Fri, 24 Apr 2020 15:15:22 +0100 Subject: [PATCH] Support singledispatch in autofunction/methodThis makes autofunction and automethod work standalone withsingledispatch functions and methods. Previously only automodule wouldsupport these --- sphinx/ext/autodoc/__init__.py | 97 +++++++++++++++++++--------------- tests/test_autodoc.py | 32 +++++++++++ 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index ac4c7ed4b..e34593226 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1062,30 +1062,15 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() - super().add_directive_header(sig) + if inspect.is_singledispatch_function(self.object): + self.add_singledispatch_directive_header(sig) + else: + super().add_directive_header(sig) if inspect.iscoroutinefunction(self.object): self.add_line(' :async:', sourcename) - -class SingledispatchFunctionDocumenter(FunctionDocumenter): - """ - Specialized Documenter subclass for singledispatch'ed functions. - """ - objtype = 'singledispatch_function' - directivetype = 'function' - member_order = 30 - - # before FunctionDocumenter - priority = FunctionDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - return (super().can_document_member(member, membername, isattr, parent) and - inspect.is_singledispatch_function(member)) - - def add_directive_header(self, sig: str) -> None: + def add_singledispatch_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() # intercept generated directive headers @@ -1125,6 +1110,26 @@ class SingledispatchFunctionDocumenter(FunctionDocumenter): func.__signature__ = sig.replace(parameters=params) # type: ignore +class SingledispatchFunctionDocumenter(FunctionDocumenter): + """ + Specialized Documenter subclass for singledispatch'ed functions. + + Retained for backwards compatibility, does the same as the FunctionDocumenter + """ + objtype = 'singledispatch_function' + directivetype = 'function' + member_order = 30 + + # before FunctionDocumenter + priority = FunctionDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return (super().can_document_member(member, membername, isattr, parent) and + inspect.is_singledispatch_function(member)) + + class DecoratorDocumenter(FunctionDocumenter): """ Specialized Documenter subclass for decorator functions. @@ -1455,7 +1460,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return args def add_directive_header(self, sig: str) -> None: - super().add_directive_header(sig) + meth = self.parent.__dict__.get(self.objpath[-1]) + if inspect.is_singledispatch_method(meth): + self.add_singledispatch_directive_header(sig) + else: + super().add_directive_header(sig) sourcename = self.get_sourcename() obj = self.parent.__dict__.get(self.object_name, self.object) @@ -1471,28 +1480,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: def document_members(self, all_members: bool = False) -> None: pass - -class SingledispatchMethodDocumenter(MethodDocumenter): - """ - Specialized Documenter subclass for singledispatch'ed methods. - """ - objtype = 'singledispatch_method' - directivetype = 'method' - member_order = 50 - - # before MethodDocumenter - priority = MethodDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - if super().can_document_member(member, membername, isattr, parent) and parent.object: - meth = parent.object.__dict__.get(membername) - return inspect.is_singledispatch_method(meth) - else: - return False - - def add_directive_header(self, sig: str) -> None: + def add_singledispatch_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() # intercept generated directive headers @@ -1533,6 +1521,29 @@ class SingledispatchMethodDocumenter(MethodDocumenter): func.__signature__ = sig.replace(parameters=params) # type: ignore +class SingledispatchMethodDocumenter(MethodDocumenter): + """ + Specialized Documenter subclass for singledispatch'ed methods. + + Retained for backwards compatibility, does the same as the MethodDocumenter + """ + objtype = 'singledispatch_method' + directivetype = 'method' + member_order = 50 + + # before MethodDocumenter + priority = MethodDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + if super().can_document_member(member, membername, isattr, parent) and parent.object: + meth = parent.object.__dict__.get(membername) + return inspect.is_singledispatch_method(meth) + else: + return False + + class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for attributes. diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index dd474536d..8e23291f3 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1604,6 +1604,21 @@ def test_singledispatch(): ] +@pytest.mark.usefixtures('setup_test') +def test_singledispatch_autofunction(): + options = {} + actual = do_autodoc(app, 'function', 'target.singledispatch.func', options) + assert list(actual) == [ + '', + '.. py:function:: func(arg, kwarg=None)', + ' func(arg: int, kwarg=None)', + ' func(arg: str, kwarg=None)', + ' :module: target.singledispatch', + '', + ' A function for general use.', + '', + ] + @pytest.mark.skipif(sys.version_info < (3, 8), reason='singledispatchmethod is available since python3.8') @pytest.mark.usefixtures('setup_test') @@ -1631,6 +1646,23 @@ def test_singledispatchmethod(): ] +@pytest.mark.skipif(sys.version_info < (3, 8), + reason='singledispatchmethod is available since python3.8') +@pytest.mark.usefixtures('setup_test') +def test_singledispatchmethod_automethod(): + options = {} + actual = do_autodoc(app, 'method', 'target.singledispatchmethod.Foo.meth', options) + assert list(actual) == [ + '', + '.. py:method:: Foo.meth(arg, kwarg=None)', + ' Foo.meth(arg: int, kwarg=None)', + ' Foo.meth(arg: str, kwarg=None)', + ' :module: target.singledispatchmethod', + '', + ' A method for general use.', + '', + ] + @pytest.mark.usefixtures('setup_test') @pytest.mark.skipif(pyximport is None, reason='cython is not installed') def test_cython():